Skip to content

Commit

Permalink
Merge pull request #1212 from bmish/property-string-literal-value
Browse files Browse the repository at this point in the history
Improve detection of property names (check string literals in addition to identifiers) in several rules
  • Loading branch information
bmish authored May 30, 2021
2 parents 10ac49f + dc2b12e commit 1f69e6a
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ function findTrackedProperties(nodeClassDeclaration, trackedImportName) {
(node) =>
types.isClassProperty(node) &&
decoratorUtils.hasDecorator(node, trackedImportName) &&
types.isIdentifier(node.key)
(types.isIdentifier(node.key) || types.isStringLiteral(node.key))
)
.map((node) => node.key.name)
.map((node) => node.key.name || node.key.value)
);
}

Expand Down
8 changes: 5 additions & 3 deletions lib/rules/no-controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ function classDeclarationHasProperty(classDeclaration, propertyName) {
classDeclaration.body.body.some(
(item) =>
types.isClassProperty(item) &&
types.isIdentifier(item.key) &&
item.key.name === propertyName
((types.isIdentifier(item.key) && item.key.name === propertyName) ||
(types.isStringLiteral(item.key) && item.key.value === propertyName))
)
);
}
Expand All @@ -69,7 +69,9 @@ function callExpressionClassHasProperty(callExpression, propertyName, scopeManag
resultingNode &&
resultingNode.type === 'ObjectExpression' &&
resultingNode.properties.some(
(prop) => types.isIdentifier(prop.key) && prop.key.name === propertyName
(prop) =>
(types.isIdentifier(prop.key) && prop.key.name === propertyName) ||
(types.isStringLiteral(prop.key) && prop.key.value === propertyName)
)
);
})
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-restricted-service-injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ module.exports = {
}
} else {
// The service name is the property name.
checkForViolationAndReport(node, node.key.name);
checkForViolationAndReport(node, node.key.name || node.key.value);
}
},

Expand All @@ -147,7 +147,7 @@ module.exports = {
serviceDecorator.expression.arguments[0].type === 'Literal' &&
typeof serviceDecorator.expression.arguments[0].value === 'string'
? serviceDecorator.expression.arguments[0].value
: node.key.name;
: node.key.name || node.key.value;

checkForViolationAndReport(node, serviceName);
},
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-unnecessary-service-injection-argument.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ module.exports = {
return;
}

const keyName = node.key.name;
const keyName = node.key.name || node.key.value;
const firstArg = node.value.arguments[0];
const firstArgValue = firstArg.value;
if (keyName === firstArgValue) {
Expand All @@ -74,7 +74,7 @@ module.exports = {
return;
}

const keyName = node.key.name;
const keyName = node.key.name || node.key.value;
const firstArg = node.decorators[0].expression.arguments[0];
const firstArgValue = firstArg.value;
if (keyName === firstArgValue) {
Expand Down
18 changes: 14 additions & 4 deletions lib/rules/no-unused-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,13 @@ module.exports = {
currentClass &&
emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)
) {
const name = node.key.name;
currentClass.services[name] = node;
if (node.key.type === 'Identifier') {
const name = node.key.name;
currentClass.services[name] = node;
} else if (types.isStringLiteral(node.key)) {
const name = node.key.value;
currentClass.services[name] = node;
}
}
},
// @service(...) foo;
Expand All @@ -337,8 +342,13 @@ module.exports = {
currentClass &&
emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)
) {
const name = node.key.name;
currentClass.services[name] = node;
if (node.key.type === 'Identifier') {
const name = node.key.name;
currentClass.services[name] = node;
} else if (types.isStringLiteral(node.key)) {
const name = node.key.value;
currentClass.services[name] = node;
}
}
},
// this.foo...
Expand Down
9 changes: 6 additions & 3 deletions lib/rules/require-computed-property-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,13 @@ function findInjectedServiceNames(node) {
}
if (
(types.isProperty(child) || types.isClassProperty(child)) &&
emberUtils.isInjectedServiceProp(child, importedEmberName, importedInjectName) &&
types.isIdentifier(child.key)
emberUtils.isInjectedServiceProp(child, importedEmberName, importedInjectName)
) {
results.push(child.key.name);
if (types.isIdentifier(child.key)) {
results.push(child.key.name);
} else if (types.isStringLiteral(child.key)) {
results.push(child.key.value);
}
}
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ ruleTester.run('no-assignment-of-untracked-properties-used-in-tracking-contexts'
}`,
filename: '/components/foo.js',
},
{
// Assignment of tracked property with string literal property name.
code: `
import { computed } from '@ember/object';
import Component from '@ember/component';
import { tracked } from '@glimmer/tracking';
class MyClass extends Component {
@tracked 'x'
@computed('x') get prop() {}
myFunction() { this.x = 123; }
}`,
filename: '/components/foo.js',
},
{
// Assignment of tracked property (with aliased `tracked` import).
code: `
Expand Down
14 changes: 14 additions & 0 deletions tests/lib/rules/no-controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ ruleTester.run('no-controllers', rule, {
queryParams: ['query', 'sortType', 'sortOrder']
});
`,
// Classic class with queryParams with string literal property name.
`
import Controller from '@ember/controller';
export default Controller.extend({
'queryParams': ['query', 'sortType', 'sortOrder']
});
`,
// Classic class with queryParams: checks object argument from variable.
`
import Controller from '@ember/controller';
Expand All @@ -48,6 +55,13 @@ ruleTester.run('no-controllers', rule, {
queryParams = ['category'];
}
`,
// Native class with queryParams with string literal property name.
`
import Controller from '@ember/controller';
export default class ArticlesController extends Controller {
'queryParams' = ['category'];
}
`,
],

invalid: [
Expand Down
16 changes: 16 additions & 0 deletions tests/lib/rules/no-restricted-service-injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ ruleTester.run('no-restricted-service-injections', rule, {
filename: 'app/components/path.js',
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],
},
{
// Without service name argument (property name as string literal):
code: `${SERVICE_IMPORT} Component.extend({ 'myService': service() })`,
options: [{ paths: ['app/components'], services: ['my-service'] }],
output: null,
filename: 'app/components/path.js',
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],
},
{
// With camelized service name argument:
code: `${SERVICE_IMPORT} Component.extend({ randomName: service('myService') })`,
Expand Down Expand Up @@ -144,6 +152,14 @@ ruleTester.run('no-restricted-service-injections', rule, {
filename: 'app/components/path.js',
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'ClassProperty' }],
},
{
// With decorator without service name argument (with parentheses) (with property name as string literal):
code: `${SERVICE_IMPORT} class MyComponent extends Component { @service() 'myService' }`,
options: [{ paths: ['app/components'], services: ['my-service'] }],
output: null,
filename: 'app/components/path.js',
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'ClassProperty' }],
},
{
// With custom error message:
code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,
Expand Down
58 changes: 7 additions & 51 deletions tests/lib/rules/no-unnecessary-service-injection-argument.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,44 +39,22 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
ecmaFeatures: { legacyDecorators: true },
},
},
{
code: `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
`${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,

// Property name matches service name but service name uses dashes
// (allowed because it avoids needless runtime camelization <-> dasherization in the resolver):
`${EMBER_IMPORT} export default Component.extend({ specialName: Ember.inject.service('service-name') });`,
`${RENAMED_SERVICE_IMPORT} export default Component.extend({ specialName: service('service-name') });`,
`${SERVICE_IMPORT} export default Component.extend({ specialName: inject('service-name') });`,
`${SERVICE_IMPORT} export default Component.extend({ 'specialName': inject('service-name') });`,
`${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service('service-name') });`,
{
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") serviceName }`,
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
`${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") serviceName }`,
`${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") 'serviceName' }`,

// Property name does not match service name:
`${EMBER_IMPORT} const controller = Controller.extend({ specialName: Ember.inject.service('service-name') });`,
`${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ specialName: service('service-name') });`,
{
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("specialName") serviceName }`,
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
`${RENAMED_SERVICE_IMPORT} class Test { @service("specialName") serviceName }`,

// When usage is ignored because of additional arguments:
`${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service('serviceName', EXTRA_PROPERTY) });`,
Expand All @@ -86,29 +64,13 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
// When usage is ignored because of template literal:
`${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service(\`serviceName\`) });`,
`${SERVICE_IMPORT} export default Component.extend({ serviceName: service(\`serviceName\`) });`,
{
code: `${RENAMED_SERVICE_IMPORT} class Test { @service(\`specialName\`) serviceName }`,
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
`${RENAMED_SERVICE_IMPORT} class Test { @service(\`specialName\`) serviceName }`,

// Not Ember's `service()` function:
"export default Component.extend({ serviceName: otherFunction('serviceName') });",
`${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service.otherFunction('serviceName') });`,
"export default Component.extend({ serviceName: inject.otherFunction('serviceName') });",
{
code: 'class Test { @otherDecorator("name") name }',
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
'class Test { @otherDecorator("name") name }',

'export default Component.extend({ ...foo });',
],
Expand Down Expand Up @@ -142,12 +104,6 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("serviceName") serviceName }`,
output: `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,
errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: { legacyDecorators: true },
},
},
],
});
4 changes: 3 additions & 1 deletion tests/lib/rules/no-unused-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ function generateValid() {
valid.push(
`${SERVICE_IMPORT} class MyClass { @service('foo') ${SERVICE_NAME}; fooFunc() {${use}} }`,
`${SERVICE_IMPORT} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${use}} }`,
`${SERVICE_IMPORT} class MyClass { @service() '${SERVICE_NAME}'; fooFunc() {${use}} }`,
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service('foo'), fooFunc() {${use}} });`,
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`,
`${SERVICE_IMPORT} Component.extend({ '${SERVICE_NAME}': service(), fooFunc() {${use}} });`
);
}

Expand Down
9 changes: 8 additions & 1 deletion tests/lib/rules/require-computed-property-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
console.log(this.intl);
return this.name + this.intl.t('some.translation.key');
console.log(this.otherService);
console.log(this.serviceNameInStringLiteral);
}),
otherService: service() // Service injection coming after computed property.
otherService: service(), // Service injection coming after computed property.
'serviceNameInStringLiteral': service() // Property name as string literal.
});
`,
// Should ignore the left side of an assignment.
Expand Down Expand Up @@ -117,8 +119,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
import { inject as service } from '@ember/service';
class Test {
@service i18n; // Service names not required as dependent keys by default.
@service 'serviceNameInStringLiteral';
@computed('first', 'last')
get fullName() { return this.i18n.t(this.first + ' ' + this.last); }
@computed()
get foo() { return this.serviceNameInStringLiteral; }
}
`,
],
Expand Down

0 comments on commit 1f69e6a

Please sign in to comment.