diff --git a/.gitignore b/.gitignore index 3e88eb2cce..bb90e4af47 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ npm-debug.log # eslint-remote-tester eslint-remote-tester-results + +# npm lock file (project uses pnpm) +package-lock.json diff --git a/lib/rules/template-missing-invokable.js b/lib/rules/template-missing-invokable.js index c13e00cff0..4024a2c62b 100644 --- a/lib/rules/template-missing-invokable.js +++ b/lib/rules/template-missing-invokable.js @@ -1,3 +1,25 @@ +'use strict'; + +// Invokables that are available in every Ember project without any extra +// packages. User-provided `invokables` config is merged on top of these so +// any entry here can be overridden by the consuming project. +const BUILTIN_INVOKABLES = { + fn: ['fn', '@ember/helper'], + get: ['get', '@ember/helper'], + hash: ['hash', '@ember/helper'], + array: ['array', '@ember/helper'], + concat: ['concat', '@ember/helper'], + htmlSafe: ['htmlSafe', '@ember/template'], + trustedHTML: ['trustedHTML', '@ember/template'], + LinkTo: ['LinkTo', '@ember/routing'], + on: ['on', '@ember/modifier'], + trackedArray: ['trackedArray', '@ember/reactive/collections'], + trackedObject: ['trackedObject', '@ember/reactive/collections'], + trackedSet: ['trackedSet', '@ember/reactive/collections'], + trackedWeakSet: ['trackedWeakSet', '@ember/reactive/collections'], + trackedWeakMap: ['trackedWeakMap', '@ember/reactive/collections'], +}; + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -34,15 +56,16 @@ module.exports = { create: (context) => { const sourceCode = context.sourceCode; + const invokables = { ...BUILTIN_INVOKABLES, ...context.options[0]?.invokables }; // takes a node with a `.path` property function checkInvokable(node) { if (node.path.type === 'GlimmerPathExpression' && node.path.tail.length === 0) { if (!isBound(node.path.head, sourceCode.getScope(node.path))) { - const matched = context.options[0]?.invokables?.[node.path.head.name]; + const matched = invokables[node.path.head.name]; if (matched) { - const [name, module] = matched; - const importStatement = buildImportStatement(node.path.head.name, name, module); + const [name, moduleName] = matched; + const importStatement = buildImportStatement(node.path.head.name, name, moduleName); context.report({ node: node.path, messageId: 'missing-invokable', diff --git a/tests/fixtures/projects/has-ember-truth-helpers/package.json b/tests/fixtures/projects/has-ember-truth-helpers/package.json new file mode 100644 index 0000000000..aa8a8d0bd3 --- /dev/null +++ b/tests/fixtures/projects/has-ember-truth-helpers/package.json @@ -0,0 +1,6 @@ +{ + "name": "has-ember-truth-helpers", + "dependencies": { + "ember-truth-helpers": "*" + } +} diff --git a/tests/lib/rules/template-missing-invokable.js b/tests/lib/rules/template-missing-invokable.js index 0aa1706021..ddcb806901 100644 --- a/tests/lib/rules/template-missing-invokable.js +++ b/tests/lib/rules/template-missing-invokable.js @@ -65,10 +65,24 @@ ruleTester.run('template-missing-invokable', rule, { `, + + // Built-in invokables are not reported when already imported + ` + import { fn } from '@ember/helper'; + + `, + ` + import { LinkTo } from '@ember/routing'; + + `, ], invalid: [ - // Subexpression invocations + // Subexpression invocations — always auto-fixes when invokable is configured { code: `