Skip to content

Commit

Permalink
add warning for constrainProportions in favor of targetAspectRatio (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
gh-owongfigma authored Feb 14, 2025
1 parent 7261892 commit a6b8588
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 13 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,16 @@ To use ESLint with VSCode, see the [ESLint VSCode extension](https://marketplace
🔦 Set in the `recommended-problems-only` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).

| Name | Description | 💼 | ⚠️ | 🔧 |
| :------------------------------------------------------------------------------------------------- | :--------------------------------------------------- | :---- | :- | :- |
| [await-requires-async](docs/rules/await-requires-async.md) | Require functions that contain `await` to be `async` | 👍 🔦 | | 🔧 |
| [ban-deprecated-id-params](docs/rules/ban-deprecated-id-params.md) | Ban use of deprecated string ID parameters | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-methods](docs/rules/ban-deprecated-sync-methods.md) | Ban use of deprecated synchronous methods | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-prop-getters](docs/rules/ban-deprecated-sync-prop-getters.md) | Ban use of deprecated synchronous property getters | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-prop-setters](docs/rules/ban-deprecated-sync-prop-setters.md) | Ban use of deprecated synchronous property getters | 👍 🔦 | | 🔧 |
| [dynamic-page-documentchange-event-advice](docs/rules/dynamic-page-documentchange-event-advice.md) | Advice on using the `documentchange` event | | 👍 | |
| [dynamic-page-find-method-advice](docs/rules/dynamic-page-find-method-advice.md) | Advice on using the find*() family of methods | | 👍 | |
| Name                                                         | Description | 💼 | ⚠️ | 🔧 |
| :----------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------- | :---- | :- | :- |
| [await-requires-async](docs/rules/await-requires-async.md) | Require functions that contain `await` to be `async` | 👍 🔦 | | 🔧 |
| [ban-deprecated-id-params](docs/rules/ban-deprecated-id-params.md) | Ban use of deprecated string ID parameters | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-methods](docs/rules/ban-deprecated-sync-methods.md) | Ban use of deprecated synchronous methods | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-prop-getters](docs/rules/ban-deprecated-sync-prop-getters.md) | Ban use of deprecated synchronous property getters | 👍 🔦 | | 🔧 |
| [ban-deprecated-sync-prop-setters](docs/rules/ban-deprecated-sync-prop-setters.md) | Ban use of deprecated synchronous property getters | 👍 🔦 | | 🔧 |
| [constrain-proportions-replaced-by-target-aspect-ratio-advice](docs/rules/constrain-proportions-replaced-by-target-aspect-ratio-advice.md) | Warns against using constrainProportions in favor of targetAspectRatio | | 👍 | |
| [dynamic-page-documentchange-event-advice](docs/rules/dynamic-page-documentchange-event-advice.md) | Advice on using the `documentchange` event | | 👍 | |
| [dynamic-page-find-method-advice](docs/rules/dynamic-page-find-method-advice.md) | Advice on using the find*() family of methods | | 👍 | |

<!-- end auto-generated rules list -->

Expand Down
6 changes: 4 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const banDeprecatedSyncMethods_1 = require("./rules/banDeprecatedSyncMethods");
const banDeprecatedSyncPropGetters_1 = require("./rules/banDeprecatedSyncPropGetters");
const banDeprecatedSyncPropSetters_1 = require("./rules/banDeprecatedSyncPropSetters");
const dynamicPageFindMethodAdvice_1 = require("./rules/dynamicPageFindMethodAdvice");
const constrainProportionsReplacedByTargetAspectRatioAdvice_1 = require("./rules/constrainProportionsReplacedByTargetAspectRatioAdvice");
function rulesetWithSeverity(severity, rules) {
return Object.keys(rules).reduce((acc, name) => {
acc[`@figma/figma-plugins/${name}`] = severity;
Expand All @@ -25,15 +26,16 @@ const dynamicePageAdvice = {
'dynamic-page-documentchange-event-advice': dynamicPageDocumentchangeEventAdvice_1.dynamicPageDocumentchangeEventAdvice,
'dynamic-page-find-method-advice': dynamicPageFindMethodAdvice_1.dynamicPageFindMethodAdvice,
};
const warnRules = Object.assign(Object.assign({}, dynamicePageAdvice), { 'constrain-proportions-replaced-by-target-aspect-ratio-advice': constrainProportionsReplacedByTargetAspectRatioAdvice_1.constrainProportionsReplacedByTargetAspectRatioAdvice });
// The exported type annotations in this file are somewhat arbitrary; we do NOT
// expect anyone to actually consume these types. We include them because we use
// @figma as a type root, and all packages under a type root must emit a type
// declaration file.
exports.rules = Object.assign(Object.assign({}, errRules), dynamicePageAdvice);
exports.rules = Object.assign(Object.assign({}, errRules), warnRules);
exports.configs = {
recommended: {
plugins: ['@figma/figma-plugins'],
rules: Object.assign(Object.assign({}, rulesetWithSeverity('error', errRules)), rulesetWithSeverity('warn', dynamicePageAdvice)),
rules: Object.assign(Object.assign({}, rulesetWithSeverity('error', errRules)), rulesetWithSeverity('warn', warnRules)),
},
'recommended-problems-only': {
plugins: ['@figma/figma-plugins'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import type { TSESLint as _ } from '@typescript-eslint/utils';
export declare const constrainProportionsReplacedByTargetAspectRatioAdvice: _.RuleModule<"readAdvice" | "writeAdvice", never[], _.RuleListener>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.constrainProportionsReplacedByTargetAspectRatioAdvice = void 0;
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
const util_1 = require("../util");
exports.constrainProportionsReplacedByTargetAspectRatioAdvice = (0, util_1.createPluginRule)({
name: 'constrain-proportions-replaced-by-target-aspect-ratio-advice',
meta: {
docs: {
description: 'Warns against using constrainProportions in favor of targetAspectRatio',
},
messages: {
readAdvice: 'Please use targetAspectRatio instead of constrainProportions for determining if a node will resize proportinally.',
writeAdvice: 'Please use lockAspectRatio() or unlockAspectRatio() instead of setting constrainProportions.',
},
schema: [],
type: 'suggestion',
},
defaultOptions: [],
create(context) {
return {
MemberExpression(node) {
const property = node.property;
if (property.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
property.name === 'constrainProportions') {
// Check if the receiver is a LayoutMixin, since that's what constrainProportions lives on
const match = (0, util_1.matchAncestorTypes)(context, node.object, ['LayoutMixin']);
if (!match) {
return;
}
// Check if it's being read or written to
const parent = node.parent;
if ((parent === null || parent === void 0 ? void 0 : parent.type) === typescript_estree_1.AST_NODE_TYPES.AssignmentExpression &&
parent.left === node) {
// It's being written to
context.report({
node,
messageId: 'writeAdvice',
});
}
else {
// It's being read
context.report({
node,
messageId: 'readAdvice',
});
}
}
},
};
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Warns against using constrainProportions in favor of targetAspectRatio (`@figma/figma-plugins/constrain-proportions-replaced-by-target-aspect-ratio-advice`)

⚠️ This rule _warns_ in the 👍 `recommended` config.

<!-- end auto-generated rule header -->
10 changes: 8 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { banDeprecatedSyncMethods } from './rules/banDeprecatedSyncMethods'
import { banDeprecatedSyncPropGetters } from './rules/banDeprecatedSyncPropGetters'
import { banDeprecatedSyncPropSetters } from './rules/banDeprecatedSyncPropSetters'
import { dynamicPageFindMethodAdvice } from './rules/dynamicPageFindMethodAdvice'
import { constrainProportionsReplacedByTargetAspectRatioAdvice } from './rules/constrainProportionsReplacedByTargetAspectRatioAdvice'

function rulesetWithSeverity(
severity: 'error' | 'warn',
Expand All @@ -29,22 +30,27 @@ const dynamicePageAdvice: Record<string, unknown> = {
'dynamic-page-find-method-advice': dynamicPageFindMethodAdvice,
}

const warnRules: Record<string, unknown> = {
...dynamicePageAdvice,
'constrain-proportions-replaced-by-target-aspect-ratio-advice': constrainProportionsReplacedByTargetAspectRatioAdvice
}

// The exported type annotations in this file are somewhat arbitrary; we do NOT
// expect anyone to actually consume these types. We include them because we use
// @figma as a type root, and all packages under a type root must emit a type
// declaration file.

export const rules: unknown = {
...errRules,
...dynamicePageAdvice,
...warnRules
}

export const configs: unknown = {
recommended: {
plugins: ['@figma/figma-plugins'],
rules: {
...rulesetWithSeverity('error', errRules),
...rulesetWithSeverity('warn', dynamicePageAdvice),
...rulesetWithSeverity('warn', warnRules),
},
},
'recommended-problems-only': {
Expand Down
59 changes: 59 additions & 0 deletions src/rules/constrainProportionsReplacedByTargetAspectRatioAdvice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'
import { createPluginRule, matchAncestorTypes } from '../util'

// Copied from dynamicPageFindMethodAdvice
// Calls to createPluginRule() cause typechecker errors without this import.
// Needed for TypeScript bug
import type { TSESLint as _ } from '@typescript-eslint/utils'

export const constrainProportionsReplacedByTargetAspectRatioAdvice = createPluginRule({
name: 'constrain-proportions-replaced-by-target-aspect-ratio-advice',
meta: {
docs: {
description: 'Warns against using constrainProportions in favor of targetAspectRatio',
},
messages: {
readAdvice: 'Please use targetAspectRatio instead of constrainProportions for determining if a node will resize proportinally.',
writeAdvice: 'Please use lockAspectRatio() or unlockAspectRatio() instead of setting constrainProportions.',
},
schema: [],
type: 'suggestion',
},
defaultOptions: [],
create(context) {
return {
MemberExpression(node: TSESTree.MemberExpression) {
const property = node.property
if (
property.type === AST_NODE_TYPES.Identifier &&
property.name === 'constrainProportions'
) {
// Check if the receiver is a LayoutMixin, since that's what constrainProportions lives on
const match = matchAncestorTypes(context, node.object, ['LayoutMixin'])
if (!match) {
return
}

// Check if it's being read or written to
const parent = node.parent
if (
parent?.type === AST_NODE_TYPES.AssignmentExpression &&
parent.left === node
) {
// It's being written to
context.report({
node,
messageId: 'writeAdvice',
})
} else {
// It's being read
context.report({
node,
messageId: 'readAdvice',
})
}
}
},
}
},
})
59 changes: 59 additions & 0 deletions test/constrainProportionsReplacedByTargetAspectRatio.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { constrainProportionsReplacedByTargetAspectRatioAdvice } from '../src/rules/constrainProportionsReplacedByTargetAspectRatioAdvice'
import { ruleTester } from './testUtil'

const types = `
interface LayoutMixin {
constrainProportions: boolean
}
interface DefaultFrameMixin extends LayoutMixin {}
interface FrameNode extends DefaultFrameMixin {}
interface SceneNode extends LayoutMixin {}
`

ruleTester().run('constrain-proportions-replaced-by-target-aspect-ratio', constrainProportionsReplacedByTargetAspectRatioAdvice, {
valid: [
{
code: `
${types}
function func(node: FrameNode) {
node.someOtherProp = true
}
`,
},
],
invalid: [
{
// Test write case
code: `
${types}
function func(node: SceneNode) {
node.constrainProportions = true
}
`,
errors: [{ messageId: 'writeAdvice' }],
},
{
// Test write case
code: `
${types}
function func(node: FrameNode) {
node.constrainProportions = false
}
`,
errors: [{ messageId: 'writeAdvice' }],
},
{
// Test read case
code: `
${types}
function func(node: SceneNode) {
const value = node.constrainProportions
}
`,
errors: [{ messageId: 'readAdvice' }],
},
],
})

0 comments on commit a6b8588

Please sign in to comment.