Skip to content

Commit 1f69e6a

Browse files
authored
Merge pull request #1212 from bmish/property-string-literal-value
Improve detection of property names (check string literals in addition to identifiers) in several rules
2 parents 10ac49f + dc2b12e commit 1f69e6a

12 files changed

+92
-69
lines changed

lib/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ function findTrackedProperties(nodeClassDeclaration, trackedImportName) {
5959
(node) =>
6060
types.isClassProperty(node) &&
6161
decoratorUtils.hasDecorator(node, trackedImportName) &&
62-
types.isIdentifier(node.key)
62+
(types.isIdentifier(node.key) || types.isStringLiteral(node.key))
6363
)
64-
.map((node) => node.key.name)
64+
.map((node) => node.key.name || node.key.value)
6565
);
6666
}
6767

lib/rules/no-controllers.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ function classDeclarationHasProperty(classDeclaration, propertyName) {
5353
classDeclaration.body.body.some(
5454
(item) =>
5555
types.isClassProperty(item) &&
56-
types.isIdentifier(item.key) &&
57-
item.key.name === propertyName
56+
((types.isIdentifier(item.key) && item.key.name === propertyName) ||
57+
(types.isStringLiteral(item.key) && item.key.value === propertyName))
5858
)
5959
);
6060
}
@@ -69,7 +69,9 @@ function callExpressionClassHasProperty(callExpression, propertyName, scopeManag
6969
resultingNode &&
7070
resultingNode.type === 'ObjectExpression' &&
7171
resultingNode.properties.some(
72-
(prop) => types.isIdentifier(prop.key) && prop.key.name === propertyName
72+
(prop) =>
73+
(types.isIdentifier(prop.key) && prop.key.name === propertyName) ||
74+
(types.isStringLiteral(prop.key) && prop.key.value === propertyName)
7375
)
7476
);
7577
})

lib/rules/no-restricted-service-injections.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ module.exports = {
123123
}
124124
} else {
125125
// The service name is the property name.
126-
checkForViolationAndReport(node, node.key.name);
126+
checkForViolationAndReport(node, node.key.name || node.key.value);
127127
}
128128
},
129129

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

152152
checkForViolationAndReport(node, serviceName);
153153
},

lib/rules/no-unnecessary-service-injection-argument.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ module.exports = {
4949
return;
5050
}
5151

52-
const keyName = node.key.name;
52+
const keyName = node.key.name || node.key.value;
5353
const firstArg = node.value.arguments[0];
5454
const firstArgValue = firstArg.value;
5555
if (keyName === firstArgValue) {
@@ -74,7 +74,7 @@ module.exports = {
7474
return;
7575
}
7676

77-
const keyName = node.key.name;
77+
const keyName = node.key.name || node.key.value;
7878
const firstArg = node.decorators[0].expression.arguments[0];
7979
const firstArgValue = firstArg.value;
8080
if (keyName === firstArgValue) {

lib/rules/no-unused-services.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,13 @@ module.exports = {
321321
currentClass &&
322322
emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)
323323
) {
324-
const name = node.key.name;
325-
currentClass.services[name] = node;
324+
if (node.key.type === 'Identifier') {
325+
const name = node.key.name;
326+
currentClass.services[name] = node;
327+
} else if (types.isStringLiteral(node.key)) {
328+
const name = node.key.value;
329+
currentClass.services[name] = node;
330+
}
326331
}
327332
},
328333
// @service(...) foo;
@@ -337,8 +342,13 @@ module.exports = {
337342
currentClass &&
338343
emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)
339344
) {
340-
const name = node.key.name;
341-
currentClass.services[name] = node;
345+
if (node.key.type === 'Identifier') {
346+
const name = node.key.name;
347+
currentClass.services[name] = node;
348+
} else if (types.isStringLiteral(node.key)) {
349+
const name = node.key.value;
350+
currentClass.services[name] = node;
351+
}
342352
}
343353
},
344354
// this.foo...

lib/rules/require-computed-property-dependencies.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,13 @@ function findInjectedServiceNames(node) {
153153
}
154154
if (
155155
(types.isProperty(child) || types.isClassProperty(child)) &&
156-
emberUtils.isInjectedServiceProp(child, importedEmberName, importedInjectName) &&
157-
types.isIdentifier(child.key)
156+
emberUtils.isInjectedServiceProp(child, importedEmberName, importedInjectName)
158157
) {
159-
results.push(child.key.name);
158+
if (types.isIdentifier(child.key)) {
159+
results.push(child.key.name);
160+
} else if (types.isStringLiteral(child.key)) {
161+
results.push(child.key.value);
162+
}
160163
}
161164
},
162165
});

tests/lib/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.js

+13
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ ruleTester.run('no-assignment-of-untracked-properties-used-in-tracking-contexts'
5454
}`,
5555
filename: '/components/foo.js',
5656
},
57+
{
58+
// Assignment of tracked property with string literal property name.
59+
code: `
60+
import { computed } from '@ember/object';
61+
import Component from '@ember/component';
62+
import { tracked } from '@glimmer/tracking';
63+
class MyClass extends Component {
64+
@tracked 'x'
65+
@computed('x') get prop() {}
66+
myFunction() { this.x = 123; }
67+
}`,
68+
filename: '/components/foo.js',
69+
},
5770
{
5871
// Assignment of tracked property (with aliased `tracked` import).
5972
code: `

tests/lib/rules/no-controllers.js

+14
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ ruleTester.run('no-controllers', rule, {
2828
queryParams: ['query', 'sortType', 'sortOrder']
2929
});
3030
`,
31+
// Classic class with queryParams with string literal property name.
32+
`
33+
import Controller from '@ember/controller';
34+
export default Controller.extend({
35+
'queryParams': ['query', 'sortType', 'sortOrder']
36+
});
37+
`,
3138
// Classic class with queryParams: checks object argument from variable.
3239
`
3340
import Controller from '@ember/controller';
@@ -48,6 +55,13 @@ ruleTester.run('no-controllers', rule, {
4855
queryParams = ['category'];
4956
}
5057
`,
58+
// Native class with queryParams with string literal property name.
59+
`
60+
import Controller from '@ember/controller';
61+
export default class ArticlesController extends Controller {
62+
'queryParams' = ['category'];
63+
}
64+
`,
5165
],
5266

5367
invalid: [

tests/lib/rules/no-restricted-service-injections.js

+16
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ ruleTester.run('no-restricted-service-injections', rule, {
8080
filename: 'app/components/path.js',
8181
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],
8282
},
83+
{
84+
// Without service name argument (property name as string literal):
85+
code: `${SERVICE_IMPORT} Component.extend({ 'myService': service() })`,
86+
options: [{ paths: ['app/components'], services: ['my-service'] }],
87+
output: null,
88+
filename: 'app/components/path.js',
89+
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],
90+
},
8391
{
8492
// With camelized service name argument:
8593
code: `${SERVICE_IMPORT} Component.extend({ randomName: service('myService') })`,
@@ -144,6 +152,14 @@ ruleTester.run('no-restricted-service-injections', rule, {
144152
filename: 'app/components/path.js',
145153
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'ClassProperty' }],
146154
},
155+
{
156+
// With decorator without service name argument (with parentheses) (with property name as string literal):
157+
code: `${SERVICE_IMPORT} class MyComponent extends Component { @service() 'myService' }`,
158+
options: [{ paths: ['app/components'], services: ['my-service'] }],
159+
output: null,
160+
filename: 'app/components/path.js',
161+
errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'ClassProperty' }],
162+
},
147163
{
148164
// With custom error message:
149165
code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,

tests/lib/rules/no-unnecessary-service-injection-argument.js

+7-51
Original file line numberDiff line numberDiff line change
@@ -39,44 +39,22 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
3939
ecmaFeatures: { legacyDecorators: true },
4040
},
4141
},
42-
{
43-
code: `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,
44-
parser: require.resolve('@babel/eslint-parser'),
45-
parserOptions: {
46-
ecmaVersion: 6,
47-
sourceType: 'module',
48-
ecmaFeatures: { legacyDecorators: true },
49-
},
50-
},
42+
`${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,
5143

5244
// Property name matches service name but service name uses dashes
5345
// (allowed because it avoids needless runtime camelization <-> dasherization in the resolver):
5446
`${EMBER_IMPORT} export default Component.extend({ specialName: Ember.inject.service('service-name') });`,
5547
`${RENAMED_SERVICE_IMPORT} export default Component.extend({ specialName: service('service-name') });`,
5648
`${SERVICE_IMPORT} export default Component.extend({ specialName: inject('service-name') });`,
49+
`${SERVICE_IMPORT} export default Component.extend({ 'specialName': inject('service-name') });`,
5750
`${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service('service-name') });`,
58-
{
59-
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") serviceName }`,
60-
parser: require.resolve('@babel/eslint-parser'),
61-
parserOptions: {
62-
ecmaVersion: 6,
63-
sourceType: 'module',
64-
ecmaFeatures: { legacyDecorators: true },
65-
},
66-
},
51+
`${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") serviceName }`,
52+
`${RENAMED_SERVICE_IMPORT} class Test { @service("service-name") 'serviceName' }`,
6753

6854
// Property name does not match service name:
6955
`${EMBER_IMPORT} const controller = Controller.extend({ specialName: Ember.inject.service('service-name') });`,
7056
`${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ specialName: service('service-name') });`,
71-
{
72-
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("specialName") serviceName }`,
73-
parser: require.resolve('@babel/eslint-parser'),
74-
parserOptions: {
75-
ecmaVersion: 6,
76-
sourceType: 'module',
77-
ecmaFeatures: { legacyDecorators: true },
78-
},
79-
},
57+
`${RENAMED_SERVICE_IMPORT} class Test { @service("specialName") serviceName }`,
8058

8159
// When usage is ignored because of additional arguments:
8260
`${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service('serviceName', EXTRA_PROPERTY) });`,
@@ -86,29 +64,13 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
8664
// When usage is ignored because of template literal:
8765
`${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service(\`serviceName\`) });`,
8866
`${SERVICE_IMPORT} export default Component.extend({ serviceName: service(\`serviceName\`) });`,
89-
{
90-
code: `${RENAMED_SERVICE_IMPORT} class Test { @service(\`specialName\`) serviceName }`,
91-
parser: require.resolve('@babel/eslint-parser'),
92-
parserOptions: {
93-
ecmaVersion: 6,
94-
sourceType: 'module',
95-
ecmaFeatures: { legacyDecorators: true },
96-
},
97-
},
67+
`${RENAMED_SERVICE_IMPORT} class Test { @service(\`specialName\`) serviceName }`,
9868

9969
// Not Ember's `service()` function:
10070
"export default Component.extend({ serviceName: otherFunction('serviceName') });",
10171
`${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service.otherFunction('serviceName') });`,
10272
"export default Component.extend({ serviceName: inject.otherFunction('serviceName') });",
103-
{
104-
code: 'class Test { @otherDecorator("name") name }',
105-
parser: require.resolve('@babel/eslint-parser'),
106-
parserOptions: {
107-
ecmaVersion: 6,
108-
sourceType: 'module',
109-
ecmaFeatures: { legacyDecorators: true },
110-
},
111-
},
73+
'class Test { @otherDecorator("name") name }',
11274

11375
'export default Component.extend({ ...foo });',
11476
],
@@ -142,12 +104,6 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
142104
code: `${RENAMED_SERVICE_IMPORT} class Test { @service("serviceName") serviceName }`,
143105
output: `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,
144106
errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],
145-
parser: require.resolve('@babel/eslint-parser'),
146-
parserOptions: {
147-
ecmaVersion: 6,
148-
sourceType: 'module',
149-
ecmaFeatures: { legacyDecorators: true },
150-
},
151107
},
152108
],
153109
});

tests/lib/rules/no-unused-services.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ function generateValid() {
139139
valid.push(
140140
`${SERVICE_IMPORT} class MyClass { @service('foo') ${SERVICE_NAME}; fooFunc() {${use}} }`,
141141
`${SERVICE_IMPORT} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${use}} }`,
142+
`${SERVICE_IMPORT} class MyClass { @service() '${SERVICE_NAME}'; fooFunc() {${use}} }`,
142143
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service('foo'), fooFunc() {${use}} });`,
143-
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`
144+
`${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`,
145+
`${SERVICE_IMPORT} Component.extend({ '${SERVICE_NAME}': service(), fooFunc() {${use}} });`
144146
);
145147
}
146148

tests/lib/rules/require-computed-property-dependencies.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
8585
console.log(this.intl);
8686
return this.name + this.intl.t('some.translation.key');
8787
console.log(this.otherService);
88+
console.log(this.serviceNameInStringLiteral);
8889
}),
89-
otherService: service() // Service injection coming after computed property.
90+
otherService: service(), // Service injection coming after computed property.
91+
'serviceNameInStringLiteral': service() // Property name as string literal.
9092
});
9193
`,
9294
// Should ignore the left side of an assignment.
@@ -117,8 +119,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
117119
import { inject as service } from '@ember/service';
118120
class Test {
119121
@service i18n; // Service names not required as dependent keys by default.
122+
@service 'serviceNameInStringLiteral';
123+
120124
@computed('first', 'last')
121125
get fullName() { return this.i18n.t(this.first + ' ' + this.last); }
126+
127+
@computed()
128+
get foo() { return this.serviceNameInStringLiteral; }
122129
}
123130
`,
124131
],

0 commit comments

Comments
 (0)