forked from ember-cli/eslint-plugin-ember
-
Notifications
You must be signed in to change notification settings - Fork 0
/
no-restricted-service-injections.js
162 lines (144 loc) · 5.19 KB
/
no-restricted-service-injections.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
'use strict';
const assert = require('assert');
const emberUtils = require('../utils/ember');
const decoratorUtils = require('../utils/decorators');
const { getImportIdentifier } = require('../utils/import');
const DEFAULT_ERROR_MESSAGE = 'Injecting this service is not allowed from this file.';
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow injecting certain services under certain paths',
category: 'Services',
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-restricted-service-injections.md',
},
schema: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: [
{
type: 'object',
required: ['services'],
properties: {
paths: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: {
type: 'string',
},
},
services: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: {
type: 'string',
},
},
message: {
type: 'string',
},
},
additionalProperties: false,
},
],
},
},
DEFAULT_ERROR_MESSAGE,
create(context) {
// Validate options.
for (const option of context.options) {
for (const service of option.services) {
assert(
service.toLowerCase() === service,
'Service name should be passed in kebab-case (all lower case)'
);
}
}
// Find matching denylist entries for this file path.
const denylists = context.options.filter(
(option) => !option.paths || option.paths.some((path) => context.getFilename().match(path))
);
if (denylists.length === 0) {
return {};
}
function checkForViolationAndReport(node, serviceName) {
const serviceNameKebabCase = emberUtils.convertServiceNameToKebabCase(serviceName); // splitting is used to avoid converting folder/ to folder-
for (const denylist of denylists) {
// Denylist services are always passed in in kebab-case, so we can do a kebab-case comparison.
if (denylist.services.includes(serviceNameKebabCase)) {
context.report({
node,
message: denylist.message || DEFAULT_ERROR_MESSAGE,
});
}
}
}
let importedInjectName;
let importedEmberName;
// Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).
// Handles:
// * @service myService
// * @service() myService
// * @service('myService') propertyName
function visitClassPropertyOrPropertyDefinition(node) {
if (!emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)) {
return;
}
// Find the service decorator.
const serviceDecorator = decoratorUtils.findDecorator(node, 'service');
// Get the service name either from the string argument or from the property name.
const serviceName =
serviceDecorator.expression.type === 'CallExpression' &&
serviceDecorator.expression.arguments &&
serviceDecorator.expression.arguments.length === 1 &&
serviceDecorator.expression.arguments[0].type === 'Literal' &&
typeof serviceDecorator.expression.arguments[0].value === 'string'
? serviceDecorator.expression.arguments[0].value
: node.key.name || node.key.value;
checkForViolationAndReport(node, serviceName);
}
return {
ImportDeclaration(node) {
if (node.source.value === 'ember') {
importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');
}
if (node.source.value === '@ember/service') {
importedInjectName =
importedInjectName ||
getImportIdentifier(node, '@ember/service', 'inject') ||
getImportIdentifier(node, '@ember/service', 'service');
}
},
// Handles:
// * myService: service()
// * propertyName: service('myService')
Property(node) {
if (!emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)) {
return;
}
const callExpression = node.value;
// Get the service name either from the string argument or from the property name.
if (callExpression.arguments && callExpression.arguments.length > 0) {
if (
callExpression.arguments[0].type === 'Literal' &&
typeof callExpression.arguments[0].value === 'string'
) {
// The service name is the string argument.
checkForViolationAndReport(node, callExpression.arguments[0].value);
} else {
// Ignore this case since the argument is not a string.
}
} else {
// The service name is the property name.
checkForViolationAndReport(node, node.key.name || node.key.value);
}
},
ClassProperty: visitClassPropertyOrPropertyDefinition,
PropertyDefinition: visitClassPropertyOrPropertyDefinition,
};
},
};