diff --git a/README.md b/README.md index 342e92ccde..c5187e15a9 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ rules in templates can be disabled with eslint directives with mustache or html | [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | | | [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | | | [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | | +| [template-no-potential-path-strings](docs/rules/template-no-potential-path-strings.md) | disallow potential path strings in attribute values | | | | | [template-no-splattributes-with-class](docs/rules/template-no-splattributes-with-class.md) | disallow splattributes with class attribute | | | | | [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md) | disallow trailing whitespace at the end of lines in templates | | 🔧 | | | [template-no-unavailable-this](docs/rules/template-no-unavailable-this.md) | disallow `this` in templates that are not inside a class or function | | | | diff --git a/docs/rules/template-no-potential-path-strings.md b/docs/rules/template-no-potential-path-strings.md new file mode 100644 index 0000000000..1b13578c28 --- /dev/null +++ b/docs/rules/template-no-potential-path-strings.md @@ -0,0 +1,41 @@ +# ember/template-no-potential-path-strings + + + +It might happen sometimes that `{{` and `}}` are forgotten when invoking a component, and the string that is passed was actually supposed to be a property path or argument. + +This rule warns about all arguments and attributes that start with `this.` or `@`, but are missing the surrounding `{{` and `}}` characters. + +## Examples + +This rule **forbids** the following: + +```hbs + +``` + +```hbs + +``` + +This rule **allows** the following: + +```hbs + +``` + +```hbs + +``` + +## Migration + +- Replace the surrounding `"` characters with `{{`/`}}` + +## Related Rules + +- [no-arguments-for-html-elements](template-no-arguments-for-html-elements.md) + +## References + +- [Component Arguments and HTML Attributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/) diff --git a/lib/rules/template-no-potential-path-strings.js b/lib/rules/template-no-potential-path-strings.js new file mode 100644 index 0000000000..2dd83d94b5 --- /dev/null +++ b/lib/rules/template-no-potential-path-strings.js @@ -0,0 +1,47 @@ +const FINE_SYMBOLS = ['|', '/', '\\']; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow potential path strings in attribute values', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-potential-path-strings.md', + templateMode: 'both', + }, + fixable: null, + schema: [], + messages: { + noPotentialPathStrings: + 'Potential path in attribute string detected. Did you mean {{{{path}}}}?', + }, + originallyFrom: { + name: 'ember-template-lint', + rule: 'lib/rules/no-potential-path-strings.js', + docs: 'docs/rule/no-potential-path-strings.md', + tests: 'test/unit/rules/no-potential-path-strings-test.js', + }, + }, + + create(context) { + return { + GlimmerAttrNode(node) { + if (!node.value || node.value.type !== 'GlimmerTextNode') { + return; + } + + const chars = node.value.chars; + const hasSpecialPrefix = chars.startsWith('this.') || chars.startsWith('@'); + + if (hasSpecialPrefix && !FINE_SYMBOLS.some((symbol) => chars.includes(symbol))) { + context.report({ + node: node.value, + messageId: 'noPotentialPathStrings', + data: { path: chars }, + }); + } + }, + }; + }, +}; diff --git a/tests/lib/rules/template-no-potential-path-strings.js b/tests/lib/rules/template-no-potential-path-strings.js new file mode 100644 index 0000000000..52e3af9d21 --- /dev/null +++ b/tests/lib/rules/template-no-potential-path-strings.js @@ -0,0 +1,154 @@ +const { RuleTester } = require('eslint'); +const rule = require('../../../lib/rules/template-no-potential-path-strings'); + +const ruleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser'), + parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, +}); + +ruleTester.run('template-no-potential-path-strings', rule, { + valid: [ + '', + '', + '', + '', + '', + '', + '', + '', + ], + + invalid: [ + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@img}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@img}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@bar}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.bar}}?', + }, + ], + }, + ], +}); + +const hbsRuleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser/hbs'), + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, +}); + +hbsRuleTester.run('template-no-potential-path-strings', rule, { + valid: [ + '', + '', + '', + '', + '', + '', + '', + '', + ], + invalid: [ + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@img}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@img}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{@bar}}?', + }, + ], + }, + { + code: '', + output: null, + errors: [ + { + message: 'Potential path in attribute string detected. Did you mean {{this.bar}}?', + }, + ], + }, + ], +});