Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warning for constrainProportions in favor of targetAspectRatio #34

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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' }],
},
],
})
Loading