From 125fccbf92b7b02fc101aac695ec150917b89017 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:46:21 -0500 Subject: [PATCH] Extract rule: template-no-inline-styles --- README.md | 1 + docs/rules/template-no-inline-styles.md | 40 +++++++++++++++++ lib/rules/template-no-inline-styles.js | 47 ++++++++++++++++++++ tests/lib/rules/template-no-inline-styles.js | 25 +++++++++++ 4 files changed, 113 insertions(+) create mode 100644 docs/rules/template-no-inline-styles.md create mode 100644 lib/rules/template-no-inline-styles.js create mode 100644 tests/lib/rules/template-no-inline-styles.js diff --git a/README.md b/README.md index f87c310bb8..ac4e88593a 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ rules in templates can be disabled with eslint directives with mustache or html | [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | | | [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | | | [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | | +| [template-no-inline-styles](docs/rules/template-no-inline-styles.md) | disallow inline styles | | | | | [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | | | | | [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | | | [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | | diff --git a/docs/rules/template-no-inline-styles.md b/docs/rules/template-no-inline-styles.md new file mode 100644 index 0000000000..cae7b58dc5 --- /dev/null +++ b/docs/rules/template-no-inline-styles.md @@ -0,0 +1,40 @@ +# ember/template-no-inline-styles + + + +Inline styles are not the best practice because they are hard to maintain and usually make the overall size of the project bigger. This rule forbids inline styles. Use CSS classes instead. + +## Examples + +This rule **forbids** the following: + +```gjs + +``` + +This rule **allows** the following: + +```gjs + +``` + +```gjs + +``` + +## Options + +| Name | Type | Default | Description | +| -------------------- | --------- | ------- | ------------------------------------------------------------------------------------- | +| `allowDynamicStyles` | `boolean` | `true` | When `true`, allows dynamic style values (e.g. `style={{...}}` or `style="{{...}}"`). | + +## Related Rules + +- [style-concatenation](style-concatenation.md) + +## References + +- [Deprecations/binding style attributes](https://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes) diff --git a/lib/rules/template-no-inline-styles.js b/lib/rules/template-no-inline-styles.js new file mode 100644 index 0000000000..ec4b86f0a1 --- /dev/null +++ b/lib/rules/template-no-inline-styles.js @@ -0,0 +1,47 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow inline styles', + category: 'Best Practices', + recommendedGjs: false, + recommendedGts: false, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-inline-styles.md', + }, + schema: [ + { + type: 'object', + properties: { + allowDynamicStyles: { type: 'boolean' }, + }, + additionalProperties: false, + }, + ], + messages: { noInlineStyles: 'Inline styles are not allowed' }, + }, + create(context) { + const options = context.options[0] || {}; + const allowDynamicStyles = + options.allowDynamicStyles === undefined ? true : options.allowDynamicStyles; + + return { + GlimmerElementNode(node) { + const styleAttr = node.attributes?.find((a) => a.name === 'style'); + if (!styleAttr) { + return; + } + + // If allowDynamicStyles is true, skip dynamic style values (MustacheStatement/ConcatStatement) + if (allowDynamicStyles) { + const valType = styleAttr.value?.type; + if (valType === 'GlimmerMustacheStatement' || valType === 'GlimmerConcatStatement') { + return; + } + } + + context.report({ node: styleAttr, messageId: 'noInlineStyles' }); + }, + }; + }, +}; diff --git a/tests/lib/rules/template-no-inline-styles.js b/tests/lib/rules/template-no-inline-styles.js new file mode 100644 index 0000000000..aee036ffed --- /dev/null +++ b/tests/lib/rules/template-no-inline-styles.js @@ -0,0 +1,25 @@ +const rule = require('../../../lib/rules/template-no-inline-styles'); +const RuleTester = require('eslint').RuleTester; + +const ruleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser'), + parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, +}); +ruleTester.run('template-no-inline-styles', rule, { + valid: [ + '', + // Test cases ported from ember-template-lint + '', + '', + '', + '', + '', + ], + invalid: [ + { + code: '', + output: null, + errors: [{ messageId: 'noInlineStyles' }], + }, + ], +});