@ethlete/eslint-plugin 0.1.0-next.1 → 0.1.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +3 -2
- package/src/configs/recommended.js +51 -10
- package/src/index.js +22 -2
- package/src/rules/angular-decorator-property-order.js +3 -15
- package/src/rules/class-constant-property.js +344 -0
- package/src/rules/class-member-order.js +431 -0
- package/src/rules/inject-member-accessibility.js +5 -198
- package/src/rules/internals/angular-member-visibility.js +233 -0
- package/src/rules/internals/implemented-contract-members.js +294 -0
- package/src/rules/no-empty-angular-metadata-arrays.js +104 -0
- package/src/rules/no-empty-newlines-between-imports.js +45 -0
- package/src/rules/no-leading-underscore-class-member.js +120 -0
- package/src/rules/no-legacy-angular-decorators.js +196 -1
- package/src/rules/no-public-property.js +47 -1
- package/src/rules/no-redundant-internal.js +94 -0
- package/src/rules/no-standalone-flag.js +95 -0
- package/src/rules/no-typed-injected-element-ref.js +88 -0
- package/src/rules/require-on-push-change-detection.js +271 -0
- package/src/rules/require-view-encapsulation-none.js +215 -52
- package/src/rules/template-member-accessibility.js +190 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
getAngularMetadata,
|
|
6
|
+
getMemberName,
|
|
7
|
+
isReferencedFromTemplateOrHost,
|
|
8
|
+
} = require('./internals/angular-member-visibility');
|
|
9
|
+
const { getImplementedContractMemberNames } = require('./internals/implemented-contract-members');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {any} node
|
|
13
|
+
*/
|
|
14
|
+
const isInjectCall = (node) => {
|
|
15
|
+
if (!node || node.type !== 'PropertyDefinition' || !node.value) return false;
|
|
16
|
+
if (node.value.type !== 'CallExpression') return false;
|
|
17
|
+
return node.value.callee.type === 'Identifier' && node.value.callee.name === 'inject';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {any} node
|
|
22
|
+
* @param {import('eslint').SourceCode} sourceCode
|
|
23
|
+
*/
|
|
24
|
+
const buildPublicFix = (node, sourceCode) => {
|
|
25
|
+
const keyToken = sourceCode.getFirstToken(node.key);
|
|
26
|
+
if (!keyToken) return null;
|
|
27
|
+
|
|
28
|
+
if (node.accessibility) {
|
|
29
|
+
const firstToken = sourceCode.getFirstToken(node);
|
|
30
|
+
if (!firstToken) return null;
|
|
31
|
+
|
|
32
|
+
return (fixer) => fixer.replaceText(firstToken, 'public');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (fixer) => fixer.insertTextBefore(keyToken, 'public ');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {any} node
|
|
40
|
+
* @param {import('eslint').SourceCode} sourceCode
|
|
41
|
+
*/
|
|
42
|
+
const buildRemoveProtectedFix = (node, sourceCode) => {
|
|
43
|
+
const protectedToken = sourceCode.getFirstToken(node, (token) => token.value === 'protected');
|
|
44
|
+
const nextToken = protectedToken ? sourceCode.getTokenAfter(protectedToken) : null;
|
|
45
|
+
|
|
46
|
+
if (!protectedToken || !nextToken) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (fixer) => fixer.removeRange([protectedToken.range[0], nextToken.range[0]]);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {any} node
|
|
55
|
+
*/
|
|
56
|
+
const isSupportedMember = (node) => {
|
|
57
|
+
if (node.type === 'PropertyDefinition') {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (node.type === 'MethodDefinition') {
|
|
62
|
+
return node.kind !== 'constructor';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
69
|
+
const templateMemberAccessibility = {
|
|
70
|
+
meta: {
|
|
71
|
+
type: 'problem',
|
|
72
|
+
docs: {
|
|
73
|
+
description:
|
|
74
|
+
'Require non-injected Angular class members referenced from templates or host bindings to use an explicit accessibility modifier, require explicit public on implicitly public surface members, and disallow unnecessary protected on other members.',
|
|
75
|
+
recommended: true,
|
|
76
|
+
},
|
|
77
|
+
fixable: 'code',
|
|
78
|
+
schema: [],
|
|
79
|
+
messages: {
|
|
80
|
+
shouldBePublic: 'Member `{{name}}` implements a public contract and should use an explicit `public` modifier.',
|
|
81
|
+
shouldBeExplicitPublic:
|
|
82
|
+
'Member `{{name}}` is implicitly public. Use an explicit `public` modifier. Keep or add `@internal` when it is only technically reachable.',
|
|
83
|
+
shouldBeExplicit:
|
|
84
|
+
'Member `{{name}}` is referenced from a template or host binding. Use `public` when it is intentionally exposed outside the component; otherwise use `protected`.',
|
|
85
|
+
shouldNotBeProtected:
|
|
86
|
+
'Member `{{name}}` should not be protected because it is not referenced from a template or host binding.',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
create(context) {
|
|
90
|
+
const sourceCode = context.sourceCode;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {any} node
|
|
94
|
+
*/
|
|
95
|
+
const checkMember = (node) => {
|
|
96
|
+
if (!isSupportedMember(node)) return;
|
|
97
|
+
if (node.type === 'PropertyDefinition' && isInjectCall(node)) return;
|
|
98
|
+
|
|
99
|
+
const classNode = node.parent && node.parent.type === 'ClassBody' ? node.parent.parent : null;
|
|
100
|
+
if (!classNode || (classNode.type !== 'ClassDeclaration' && classNode.type !== 'ClassExpression')) return;
|
|
101
|
+
if (!getAngularMetadata(classNode)) return;
|
|
102
|
+
|
|
103
|
+
const memberName = getMemberName(node);
|
|
104
|
+
if (!memberName) return;
|
|
105
|
+
|
|
106
|
+
const implementedContractMembers = getImplementedContractMemberNames({ classNode, context });
|
|
107
|
+
const implementsPublicContract = implementedContractMembers.has(memberName);
|
|
108
|
+
|
|
109
|
+
if (implementsPublicContract) {
|
|
110
|
+
if (node.accessibility === 'public') return;
|
|
111
|
+
|
|
112
|
+
context.report({
|
|
113
|
+
node,
|
|
114
|
+
messageId: 'shouldBePublic',
|
|
115
|
+
data: { name: memberName },
|
|
116
|
+
fix: buildPublicFix(node, sourceCode),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isTemplateVisible = isReferencedFromTemplateOrHost(classNode, memberName, context);
|
|
123
|
+
|
|
124
|
+
if (isTemplateVisible) {
|
|
125
|
+
if (node.accessibility === 'protected' || node.accessibility === 'public') return;
|
|
126
|
+
|
|
127
|
+
context.report({
|
|
128
|
+
node,
|
|
129
|
+
messageId: 'shouldBeExplicit',
|
|
130
|
+
data: { name: memberName },
|
|
131
|
+
fix: buildPublicFix(node, sourceCode),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (node.accessibility !== 'protected') return;
|
|
138
|
+
|
|
139
|
+
context.report({
|
|
140
|
+
node,
|
|
141
|
+
messageId: 'shouldNotBeProtected',
|
|
142
|
+
data: { name: memberName },
|
|
143
|
+
fix: buildRemoveProtectedFix(node, sourceCode),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {any} node
|
|
151
|
+
*/
|
|
152
|
+
const checkImplicitPublicMember = (node) => {
|
|
153
|
+
if (!isSupportedMember(node)) return;
|
|
154
|
+
if (node.type === 'PropertyDefinition' && isInjectCall(node)) return;
|
|
155
|
+
|
|
156
|
+
const classNode = node.parent && node.parent.type === 'ClassBody' ? node.parent.parent : null;
|
|
157
|
+
if (!classNode || (classNode.type !== 'ClassDeclaration' && classNode.type !== 'ClassExpression')) return;
|
|
158
|
+
if (!getAngularMetadata(classNode)) return;
|
|
159
|
+
|
|
160
|
+
const memberName = getMemberName(node);
|
|
161
|
+
if (!memberName) return;
|
|
162
|
+
|
|
163
|
+
const implementedContractMembers = getImplementedContractMemberNames({ classNode, context });
|
|
164
|
+
if (implementedContractMembers.has(memberName)) return;
|
|
165
|
+
|
|
166
|
+
if (isReferencedFromTemplateOrHost(classNode, memberName, context)) return;
|
|
167
|
+
if (node.accessibility) return;
|
|
168
|
+
|
|
169
|
+
context.report({
|
|
170
|
+
node,
|
|
171
|
+
messageId: 'shouldBeExplicitPublic',
|
|
172
|
+
data: { name: memberName },
|
|
173
|
+
fix: buildPublicFix(node, sourceCode),
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
PropertyDefinition(node) {
|
|
179
|
+
checkMember(node);
|
|
180
|
+
checkImplicitPublicMember(node);
|
|
181
|
+
},
|
|
182
|
+
MethodDefinition(node) {
|
|
183
|
+
checkMember(node);
|
|
184
|
+
checkImplicitPublicMember(node);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
module.exports = templateMemberAccessibility;
|