@ethlete/eslint-plugin 1.0.0-next.13 → 1.0.0-next.14
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
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`423b30b`](https://github.com/ethlete-io/ethdk/commit/423b30ba4f368d7cee8c464ebd89b5df20d3934c) Thanks [@TomTomB](https://github.com/TomTomB)! - The `static` class member ban now excepts `ngTemplateContextGuard`, which Angular's template type checker requires to be static.
|
|
8
|
+
|
|
9
|
+
- [`7ca77d4`](https://github.com/ethlete-io/ethdk/commit/7ca77d4a9e1dd3abad7237227c62643159719b74) Thanks [@TomTomB](https://github.com/TomTomB)! - `no-trivial-return-type`: self-referencing (recursive) functions keep their return type annotation — TypeScript cannot infer a return type that depends on itself (TS7023), so the fixer no longer strips it there.
|
|
10
|
+
|
|
3
11
|
## 1.0.0-next.13
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -139,9 +139,10 @@ const recommendedTs = {
|
|
|
139
139
|
selector: 'FunctionExpression[async=true]',
|
|
140
140
|
message: 'No async/await. Use RxJS for all async operations.',
|
|
141
141
|
},
|
|
142
|
-
// No static class members
|
|
142
|
+
// No static class members — except ngTemplateContextGuard, which Angular's template
|
|
143
|
+
// type checker requires to be static (it types a structural directive's `let-` bindings)
|
|
143
144
|
{
|
|
144
|
-
selector:
|
|
145
|
+
selector: "MethodDefinition[static=true]:not([key.name='ngTemplateContextGuard'])",
|
|
145
146
|
message: 'No static class members.',
|
|
146
147
|
},
|
|
147
148
|
{
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* ALSO GOOD (non-trivial, keep it):
|
|
13
13
|
* const parse = (): Date => new Date(value)
|
|
14
|
+
*
|
|
15
|
+
* Self-referencing (recursive) functions are exempt: TypeScript cannot infer a
|
|
16
|
+
* return type that depends on itself (TS7023 under noImplicitAny), so the
|
|
17
|
+
* annotation is required there:
|
|
18
|
+
* const walk = (node): boolean => node.ok && walk(node.next)
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
const TRIVIAL_TYPES = new Set([
|
|
@@ -69,6 +74,93 @@ const noTrivialReturnType = {
|
|
|
69
74
|
return false;
|
|
70
75
|
};
|
|
71
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The names under which a function implementation can call itself: its own name
|
|
79
|
+
* (named function expression, or the variable an arrow is assigned to) and/or
|
|
80
|
+
* `this.<key>` for class/object methods and class-field arrows.
|
|
81
|
+
* @param {any} fn
|
|
82
|
+
*/
|
|
83
|
+
const selfReferenceNames = (fn) => {
|
|
84
|
+
/** @type {{ kind: 'name' | 'this', name: string }[]} */
|
|
85
|
+
const names = [];
|
|
86
|
+
|
|
87
|
+
if (fn.id && fn.id.type === 'Identifier') {
|
|
88
|
+
names.push({ kind: 'name', name: fn.id.name });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parent = fn.parent;
|
|
92
|
+
|
|
93
|
+
if (parent) {
|
|
94
|
+
if (
|
|
95
|
+
(parent.type === 'MethodDefinition' || parent.type === 'PropertyDefinition' || parent.type === 'Property') &&
|
|
96
|
+
!parent.computed &&
|
|
97
|
+
parent.key &&
|
|
98
|
+
parent.key.type === 'Identifier'
|
|
99
|
+
) {
|
|
100
|
+
names.push({ kind: 'this', name: parent.key.name });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (parent.type === 'VariableDeclarator' && parent.id && parent.id.type === 'Identifier') {
|
|
104
|
+
names.push({ kind: 'name', name: parent.id.name });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return names;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Whether the function body references the function itself (recursion) — there the
|
|
113
|
+
* annotation is required, since TypeScript cannot infer a self-dependent return type.
|
|
114
|
+
* @param {any} fn
|
|
115
|
+
*/
|
|
116
|
+
const isSelfReferencing = (fn) => {
|
|
117
|
+
const names = selfReferenceNames(fn);
|
|
118
|
+
|
|
119
|
+
if (!names.length || !fn.body) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const stack = [fn.body];
|
|
124
|
+
|
|
125
|
+
while (stack.length) {
|
|
126
|
+
const current = stack.pop();
|
|
127
|
+
|
|
128
|
+
if (!current || typeof current !== 'object') continue;
|
|
129
|
+
|
|
130
|
+
if (Array.isArray(current)) {
|
|
131
|
+
stack.push(...current);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof current.type !== 'string') continue;
|
|
136
|
+
|
|
137
|
+
for (const { kind, name } of names) {
|
|
138
|
+
if (kind === 'name' && current.type === 'Identifier' && current.name === name) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
kind === 'this' &&
|
|
144
|
+
current.type === 'MemberExpression' &&
|
|
145
|
+
current.object.type === 'ThisExpression' &&
|
|
146
|
+
!current.computed &&
|
|
147
|
+
current.property.type === 'Identifier' &&
|
|
148
|
+
current.property.name === name
|
|
149
|
+
) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const key of Object.keys(current)) {
|
|
155
|
+
if (key === 'parent') continue;
|
|
156
|
+
const value = current[key];
|
|
157
|
+
if (value && typeof value === 'object') stack.push(value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
|
|
72
164
|
/**
|
|
73
165
|
* @param {import('eslint').Rule.Node} node
|
|
74
166
|
*/
|
|
@@ -81,6 +173,7 @@ const noTrivialReturnType = {
|
|
|
81
173
|
|
|
82
174
|
const annotation = fn.returnType.typeAnnotation;
|
|
83
175
|
if (!TRIVIAL_TYPES.has(annotation.type)) return;
|
|
176
|
+
if (isSelfReferencing(fn)) return;
|
|
84
177
|
|
|
85
178
|
const label = TRIVIAL_LABELS[annotation.type] ?? annotation.type;
|
|
86
179
|
|