@arcgis/eslint-config 5.2.0-next.3 → 5.2.0-next.30
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/dist/config/applications.d.ts +4 -3
- package/dist/config/applications.js +4 -4
- package/dist/config/extra.d.ts +4 -24
- package/dist/config/extra.js +2 -2
- package/dist/config/index.d.ts +3 -2
- package/dist/config/index.js +7 -10
- package/dist/config/lumina.d.ts +4 -3
- package/dist/config/lumina.js +8 -8
- package/dist/{estree-DxTty3dW.js → estree-CDc-die8.js} +1 -39
- package/dist/makePlugin-CA5LDDI7.js +41 -0
- package/dist/plugins/core/index.d.ts +4 -0
- package/dist/plugins/core/index.js +717 -0
- package/dist/plugins/lumina/index.d.ts +4 -5
- package/dist/plugins/lumina/index.js +2 -1
- package/dist/plugins/utils/makePlugin.d.ts +15 -16
- package/dist/plugins/webgis/index.d.ts +4 -5
- package/dist/plugins/webgis/index.js +795 -119
- package/dist/utils/defineBoundaries.d.ts +22 -38
- package/dist/utils/disableRules.d.ts +6 -6
- package/package.json +4 -2
- package/dist/config/storybook.d.ts +0 -2
- package/dist/plugins/lumina/plugin.d.ts +0 -8
- package/dist/plugins/lumina/rules/add-missing-jsx-import.d.ts +0 -2
- package/dist/plugins/lumina/rules/auto-add-type.d.ts +0 -3
- package/dist/plugins/lumina/rules/ban-events.d.ts +0 -6
- package/dist/plugins/lumina/rules/component-placement-rules.d.ts +0 -2
- package/dist/plugins/lumina/rules/consistent-event-naming.d.ts +0 -15
- package/dist/plugins/lumina/rules/consistent-nullability.d.ts +0 -2
- package/dist/plugins/lumina/rules/decorators-context.d.ts +0 -2
- package/dist/plugins/lumina/rules/explicit-setter-type.d.ts +0 -19
- package/dist/plugins/lumina/rules/member-ordering/build.d.ts +0 -4
- package/dist/plugins/lumina/rules/member-ordering/comments.d.ts +0 -19
- package/dist/plugins/lumina/rules/member-ordering/config.d.ts +0 -36
- package/dist/plugins/lumina/rules/member-ordering/normalize.d.ts +0 -10
- package/dist/plugins/lumina/rules/member-ordering.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-create-element-component.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-ignore-jsdoc-tag.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-incorrect-dynamic-tag-name.d.ts +0 -3
- package/dist/plugins/lumina/rules/no-inline-arrow-in-ref.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-inline-exposure-jsdoc-tag.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-invalid-directives-prop.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-jsdoc-xref-links.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-jsx-spread.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-listen-in-connected-callback.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-non-component-exports.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-property-name-start-with-on.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-render-false.d.ts +0 -3
- package/dist/plugins/lumina/rules/no-unnecessary-assertion-on-event.d.ts +0 -3
- package/dist/plugins/lumina/rules/no-unnecessary-attribute-name.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-unnecessary-bind-this.d.ts +0 -2
- package/dist/plugins/lumina/rules/no-unnecessary-key.d.ts +0 -2
- package/dist/plugins/lumina/rules/tag-name-rules.d.ts +0 -8
- package/dist/plugins/lumina/utils/checker.d.ts +0 -4
- package/dist/plugins/lumina/utils/estree.d.ts +0 -33
- package/dist/plugins/lumina/utils/tags.d.ts +0 -14
- package/dist/plugins/utils/helpers.d.ts +0 -2
- package/dist/plugins/webgis/plugin.d.ts +0 -8
- package/dist/plugins/webgis/rules/consistent-logging.d.ts +0 -2
- package/dist/plugins/webgis/rules/no-dts-files.d.ts +0 -2
- package/dist/plugins/webgis/rules/no-import-outside-src.d.ts +0 -2
- package/dist/plugins/webgis/rules/no-story-render-args-type-annotation.d.ts +0 -2
- package/dist/plugins/webgis/rules/no-touching-jsdoc.d.ts +0 -2
- package/dist/plugins/webgis/rules/no-unsafe-hash-links.d.ts +0 -2
- package/dist/plugins/webgis/rules/require-js-in-imports.d.ts +0 -2
|
@@ -1,111 +1,165 @@
|
|
|
1
|
-
import { m as makeEslintPlugin
|
|
1
|
+
import { m as makeEslintPlugin } from "../../makePlugin-CA5LDDI7.js";
|
|
2
|
+
import { TSESTree, AST_TOKEN_TYPES, AST_NODE_TYPES, TSESLint } from "@typescript-eslint/utils";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { resolve } from "path/posix";
|
|
3
|
-
import
|
|
5
|
+
import globals from "globals";
|
|
6
|
+
import { uncapitalize, kebabToPascal } from "@arcgis/toolkit/string";
|
|
7
|
+
import { e as extractDeclareElementsInterface, g as getComponentDeclaration } from "../../estree-CDc-die8.js";
|
|
4
8
|
const plugin = makeEslintPlugin(
|
|
5
9
|
"webgis",
|
|
6
10
|
(rule) => `https://devtopia.esri.com/WebGIS/arcgis-web-components/tree/main/packages/support-packages/eslint-config/src/plugins/webgis/rules/${rule}.ts`
|
|
7
11
|
);
|
|
8
12
|
const isTestFile = (filePath) => filePath.includes("/test") || filePath.includes(".test") || filePath.includes(".spec") || filePath.includes("e2e") || filePath.includes("__") || filePath.includes("/.");
|
|
9
13
|
const isStorybookFile = (filePath) => filePath.includes(".stories");
|
|
10
|
-
|
|
14
|
+
function hasPublicJsDocTag(sourceCode, node) {
|
|
15
|
+
const jsDocOwner = node.decorators?.at(0) ?? (node.parent?.type === TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent?.type === TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration ? node.parent : void 0) ?? node;
|
|
16
|
+
const commentTarget = jsDocOwner;
|
|
17
|
+
if (commentTarget === void 0) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return sourceCode.getCommentsBefore(commentTarget).some((comment) => comment.type === AST_TOKEN_TYPES.Block && sourceCode.getText(comment).includes("* @public"));
|
|
21
|
+
}
|
|
11
22
|
plugin.createRule({
|
|
12
|
-
name: "
|
|
23
|
+
name: "consistent-constructor-arguments",
|
|
13
24
|
meta: {
|
|
14
25
|
docs: {
|
|
15
|
-
description:
|
|
16
|
-
|
|
26
|
+
description: "Consistent constructor arguments for Accessor-based classes.",
|
|
27
|
+
// Disabled by default because the rule is most relevant for autocasting
|
|
28
|
+
// and only on public classes. We are discouraging autocasting in new
|
|
29
|
+
// code.
|
|
30
|
+
defaultLevel: "off"
|
|
17
31
|
},
|
|
18
32
|
messages: {
|
|
19
|
-
|
|
33
|
+
name: `By convention, the first parameter on an Accessor-based class must be called "properties". This is needed for autocasting - https://webgis.esri.com/references/api-extractor/api-node-kinds#constructor. If your class has constructors overloads, at least one of them must have a "properties" parameter.`,
|
|
34
|
+
type: `By convention, the constructor argument type of Accessor-based classes must be called \`{{ expectedName }}\` (found \`{{ actualName }}\` instead).
|
|
35
|
+
|
|
36
|
+
If you wish to use a loose type like any/object, create a type alias \`type {{ expectedName }}\` and assign such loose type to that.`
|
|
20
37
|
},
|
|
21
|
-
type: "
|
|
38
|
+
type: "suggestion",
|
|
22
39
|
schema: []
|
|
23
40
|
},
|
|
24
41
|
defaultOptions: [],
|
|
25
42
|
create(context) {
|
|
26
|
-
|
|
27
|
-
const srcIndex = fileName.indexOf("/src/");
|
|
28
|
-
if (srcIndex === -1) {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
const basePath = fileName.slice(0, srcIndex + "/src/".length);
|
|
32
|
-
if (isTestFile(fileName) || isStorybookFile(fileName)) {
|
|
43
|
+
if (isTestFile(context.filename)) {
|
|
33
44
|
return {};
|
|
34
45
|
}
|
|
35
46
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const isRelativeAncestor = specifier.startsWith("../");
|
|
39
|
-
if (!isRelativeAncestor) {
|
|
47
|
+
ClassDeclaration(node) {
|
|
48
|
+
if (!hasPublicJsDocTag(context.sourceCode, node)) {
|
|
40
49
|
return;
|
|
41
50
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
51
|
+
const hasSubclassDecorator = node.decorators?.some(
|
|
52
|
+
(decorator) => decorator.expression.type === TSESTree.AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === TSESTree.AST_NODE_TYPES.Identifier && decorator.expression.callee.name === "subclass"
|
|
53
|
+
);
|
|
54
|
+
const className = node.id?.name;
|
|
55
|
+
const isTopLevelClass = node.parent?.type === TSESTree.AST_NODE_TYPES.Program || node.parent?.type === TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent?.type === TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration;
|
|
56
|
+
if (!hasSubclassDecorator || className === void 0 || !isTopLevelClass) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let firstConstructor;
|
|
60
|
+
let hasPropertiesParameter = false;
|
|
61
|
+
node.body.body.forEach((element) => {
|
|
62
|
+
if (element.type !== TSESTree.AST_NODE_TYPES.MethodDefinition || element.kind !== "constructor") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
firstConstructor ??= element;
|
|
66
|
+
const firstParameterNode = element.value.params.at(0);
|
|
67
|
+
const firstParameter = firstParameterNode?.type === TSESTree.AST_NODE_TYPES.AssignmentPattern ? firstParameterNode.left : firstParameterNode;
|
|
68
|
+
const isPropertiesParameter = firstParameter?.type === TSESTree.AST_NODE_TYPES.Identifier && firstParameter.name === "properties";
|
|
69
|
+
hasPropertiesParameter ||= isPropertiesParameter;
|
|
70
|
+
if (!isPropertiesParameter) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const typeAnnotation = firstParameter.typeAnnotation?.typeAnnotation;
|
|
74
|
+
const typeAnnotationName = typeAnnotation?.type === TSESTree.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === TSESTree.AST_NODE_TYPES.Identifier ? typeAnnotation.typeName.name : void 0;
|
|
75
|
+
const expectedTypeName = `${className}Properties`;
|
|
76
|
+
if (typeAnnotationName === expectedTypeName) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const extendsText = node.superClass ? context.sourceCode.getText(node.superClass) : "";
|
|
80
|
+
const isCollectionClass = extendsText.includes("Collection.ofType") || extendsText.includes("(Collection)") || extendsText === "Collection" || extendsText === "OwningCollection" || // TODO: drop the esri/ case once core is in the monorepo
|
|
81
|
+
context.filename.endsWith(`esri${path.sep}core${path.sep}Collection.ts`) || context.filename.endsWith(`src${path.sep}core${path.sep}Collection.ts`);
|
|
82
|
+
if (isCollectionClass) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const typeIsTypeParameter = node.typeParameters?.params.some(
|
|
86
|
+
(parameter) => parameter.name.name === typeAnnotationName
|
|
87
|
+
);
|
|
88
|
+
if (typeIsTypeParameter) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
44
91
|
context.report({
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
node: typeAnnotation ?? firstParameter,
|
|
93
|
+
messageId: "type",
|
|
94
|
+
data: {
|
|
95
|
+
actualName: typeAnnotation === void 0 ? "<empty>" : context.sourceCode.getText(firstParameter.typeAnnotation?.typeAnnotation),
|
|
96
|
+
expectedName: expectedTypeName
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
if (firstConstructor === void 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!hasPropertiesParameter) {
|
|
104
|
+
context.report({
|
|
105
|
+
node: firstConstructor,
|
|
106
|
+
messageId: "name"
|
|
47
107
|
});
|
|
108
|
+
return;
|
|
48
109
|
}
|
|
49
110
|
}
|
|
50
111
|
};
|
|
51
112
|
}
|
|
52
113
|
});
|
|
53
|
-
const description$
|
|
114
|
+
const description$6 = `Enforce consistent logging so that ArcGIS developers can easily debug errors or warnings logged by our web components, which may lack a meaningful context in compiled code. See [our documentation on @arcgis/toolkit/log](https://webgis.esri.com/references/toolkit/log).`;
|
|
54
115
|
plugin.createRule({
|
|
55
|
-
name: "
|
|
116
|
+
name: "consistent-logging",
|
|
56
117
|
meta: {
|
|
57
118
|
docs: {
|
|
58
|
-
description: description$
|
|
59
|
-
defaultLevel: "
|
|
119
|
+
description: description$6,
|
|
120
|
+
defaultLevel: "off"
|
|
60
121
|
},
|
|
61
122
|
messages: {
|
|
62
|
-
|
|
123
|
+
consistentLoggingWarning: `For consistency, use the log utility from @arcgis/toolkit/log instead of console.{{logMethodName}}.`
|
|
63
124
|
},
|
|
64
|
-
|
|
65
|
-
|
|
125
|
+
schema: [],
|
|
126
|
+
fixable: void 0,
|
|
127
|
+
type: "suggestion"
|
|
66
128
|
},
|
|
67
129
|
defaultOptions: [],
|
|
68
130
|
create(context) {
|
|
131
|
+
if (isTestFile(context.filename) || isStorybookFile(context.filename)) {
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
69
134
|
return {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return void 0;
|
|
75
|
-
}
|
|
76
|
-
const previousJsDocContent = context.sourceCode.text.slice(
|
|
77
|
-
previousJsDocStart + "/**".length,
|
|
78
|
-
match.index - "*/".length
|
|
79
|
-
);
|
|
80
|
-
const containsOtherComments = previousJsDocContent.includes("*/");
|
|
81
|
-
if (containsOtherComments) {
|
|
82
|
-
return void 0;
|
|
83
|
-
}
|
|
135
|
+
// NOTE: CallExpression is not enough. Use MemberExpression to handle callbacks, e.g. .catch(console.error)
|
|
136
|
+
MemberExpression(node) {
|
|
137
|
+
if (node.type === AST_NODE_TYPES.MemberExpression && node.object.type === AST_NODE_TYPES.Identifier && node.object.name === "console" && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
138
|
+
const logMethodName = node.property.name;
|
|
84
139
|
context.report({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
node,
|
|
141
|
+
messageId: "consistentLoggingWarning",
|
|
142
|
+
data: {
|
|
143
|
+
logMethodName
|
|
89
144
|
}
|
|
90
145
|
});
|
|
91
|
-
|
|
92
|
-
});
|
|
146
|
+
}
|
|
93
147
|
}
|
|
94
148
|
};
|
|
95
149
|
}
|
|
96
150
|
});
|
|
97
|
-
const reTouchingJsDoc = /\*\/\s+\/\*\*/gu;
|
|
98
|
-
const description$4 = `@arcgis/core imports need to end with .js for better compatibility with ESM CDN builds for @arcgis/core and other packages.`;
|
|
99
|
-
const packagesToEnforce = ["@arcgis/core/", "@amcharts/amcharts4/", "@amcharts/amcharts5/"];
|
|
100
151
|
plugin.createRule({
|
|
101
|
-
name: "
|
|
152
|
+
name: "consistent-mixin-syntax",
|
|
102
153
|
meta: {
|
|
103
154
|
docs: {
|
|
104
|
-
description:
|
|
105
|
-
|
|
155
|
+
description: "Enforce consistent mixin pattern. See https://webgis.esri.com/sdk/contributing/core/core/mixins",
|
|
156
|
+
// Enabled by default because the rule targets a very specific pattern
|
|
157
|
+
// and that pattern is tricky to get right without this rule.
|
|
158
|
+
defaultLevel: "error"
|
|
106
159
|
},
|
|
107
160
|
messages: {
|
|
108
|
-
|
|
161
|
+
updateMixinCode: "Mixin typings need to be updated.",
|
|
162
|
+
typeAnnotationRequired: "Explicit type annotation is required."
|
|
109
163
|
},
|
|
110
164
|
type: "problem",
|
|
111
165
|
fixable: "code",
|
|
@@ -113,43 +167,330 @@ plugin.createRule({
|
|
|
113
167
|
},
|
|
114
168
|
defaultOptions: [],
|
|
115
169
|
create(context) {
|
|
116
|
-
|
|
170
|
+
const sourceCode = context.sourceCode.getText();
|
|
171
|
+
const isMixin = sourceCode.includes("TBase");
|
|
172
|
+
if (!isMixin) {
|
|
117
173
|
return {};
|
|
118
174
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
return {
|
|
176
|
+
VariableDeclarator(node) {
|
|
177
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier || node.init?.type !== AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const body = node.init.body;
|
|
181
|
+
if (body.type !== AST_NODE_TYPES.BlockStatement) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const firstTypeParameter = node.init.typeParameters?.params[0];
|
|
185
|
+
if (firstTypeParameter?.name.name !== "TBase") {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
let baseName = node.id.name;
|
|
189
|
+
if (baseName.startsWith("Exported")) {
|
|
190
|
+
baseName = baseName.slice("Exported".length);
|
|
191
|
+
}
|
|
192
|
+
if (baseName.endsWith("Mixin")) {
|
|
193
|
+
baseName = baseName.slice(0, -"Mixin".length);
|
|
194
|
+
}
|
|
195
|
+
const restTypeParameters = node.init.typeParameters?.params?.slice(1).map((param) => context.sourceCode.getText(param)).join(", ") ?? "";
|
|
196
|
+
const formattedRestTypeParameters = restTypeParameters.length === 0 ? "" : `<${restTypeParameters}>`;
|
|
197
|
+
const restTypeArguments = node.init.typeParameters?.params?.slice(1).map((param) => context.sourceCode.getText(param.name)).join(", ") ?? "";
|
|
198
|
+
const formattedRestTypeArguments = restTypeArguments.length === 0 ? "" : `<${restTypeArguments}>`;
|
|
199
|
+
{
|
|
200
|
+
const returnTypeCode = `typeof Public${baseName}${formattedRestTypeArguments} & TBase`;
|
|
201
|
+
if (node.init.returnType === void 0) {
|
|
202
|
+
const closeParenToken = context.sourceCode.getFirstToken(node.init, (token) => token.value === ")");
|
|
203
|
+
if (closeParenToken !== null) {
|
|
204
|
+
context.report({
|
|
205
|
+
node: node.init,
|
|
206
|
+
messageId: "updateMixinCode",
|
|
207
|
+
fix: (fixer) => {
|
|
208
|
+
const returnTypeText = `: ${returnTypeCode}`;
|
|
209
|
+
return fixer.insertTextAfter(closeParenToken, returnTypeText);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
} else if (context.sourceCode.getText(node.init.returnType.typeAnnotation) !== returnTypeCode) {
|
|
214
|
+
const typeAnnotation = node.init.returnType.typeAnnotation;
|
|
215
|
+
context.report({
|
|
216
|
+
node: typeAnnotation,
|
|
217
|
+
messageId: "updateMixinCode",
|
|
218
|
+
fix: (fixer) => fixer.replaceText(typeAnnotation, returnTypeCode)
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
let hadMissingTypeError = false;
|
|
223
|
+
let hadTypedBaseVariable = false;
|
|
224
|
+
let privateClassName = baseName;
|
|
225
|
+
let isAbstract = false;
|
|
226
|
+
const members = [];
|
|
227
|
+
for (const statement of body.body) {
|
|
228
|
+
if (statement.type === AST_NODE_TYPES.VariableDeclaration && statement.declarations.length === 1 && statement.declarations[0].id.type === AST_NODE_TYPES.Identifier && statement.declarations[0].id.name === "TypedBase" && statement.declarations[0].init?.type === AST_NODE_TYPES.TSAsExpression) {
|
|
229
|
+
hadTypedBaseVariable = true;
|
|
230
|
+
} else if (statement.type === AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
231
|
+
for (const member of statement.body.body) {
|
|
232
|
+
members.push(context.sourceCode.getText(member));
|
|
233
|
+
}
|
|
234
|
+
} else if (statement.type === AST_NODE_TYPES.ClassDeclaration) {
|
|
235
|
+
privateClassName = statement.id.name;
|
|
236
|
+
isAbstract = statement.abstract;
|
|
237
|
+
{
|
|
238
|
+
const extendsPart = statement.superClass;
|
|
239
|
+
if (extendsPart === null) {
|
|
240
|
+
context.report({
|
|
241
|
+
node: statement,
|
|
242
|
+
messageId: "updateMixinCode",
|
|
243
|
+
fix: (fixer) => fixer.insertTextAfter(statement.id, ` extends TypedBase`)
|
|
244
|
+
});
|
|
245
|
+
} else if (context.sourceCode.getText(extendsPart) !== "TypedBase") {
|
|
246
|
+
context.report({
|
|
247
|
+
node: statement,
|
|
248
|
+
messageId: "updateMixinCode",
|
|
249
|
+
fix: (fixer) => fixer.replaceText(extendsPart, "TypedBase")
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const member of statement.body.body) {
|
|
254
|
+
const isAbstractMethod = member.type === AST_NODE_TYPES.TSAbstractMethodDefinition;
|
|
255
|
+
const isAbstractProperty = member.type === AST_NODE_TYPES.TSAbstractPropertyDefinition;
|
|
256
|
+
if (member.type === AST_NODE_TYPES.MethodDefinition || isAbstractMethod) {
|
|
257
|
+
if (member.key.type === AST_NODE_TYPES.PrivateIdentifier) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const memberName = context.sourceCode.getText(member.key);
|
|
261
|
+
const formattedMemberName = member.computed ? `[${memberName}]` : memberName;
|
|
262
|
+
if (memberName === "constructor") {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const modifiers = `${member.static ? "static " : ""}${member.accessibility ? `${member.accessibility} ` : ""}${isAbstractMethod ? "abstract " : ""}`;
|
|
266
|
+
const kindName = member.kind === "get" || member.kind === "set" ? `${member.kind} ` : "";
|
|
267
|
+
const functionString = functionToTypeString(
|
|
268
|
+
member.value,
|
|
269
|
+
context,
|
|
270
|
+
member.kind === "set" ? "set" : void 0
|
|
271
|
+
);
|
|
272
|
+
if (functionString === void 0) {
|
|
273
|
+
hadMissingTypeError = true;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
members.push(`${modifiers}${kindName}${formattedMemberName}${functionString};`);
|
|
277
|
+
} else if (member.type === AST_NODE_TYPES.PropertyDefinition || isAbstractProperty) {
|
|
278
|
+
if (member.key.type === AST_NODE_TYPES.PrivateIdentifier) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const memberName = context.sourceCode.getText(member.key);
|
|
282
|
+
const formattedMemberName = member.computed ? `[${memberName}]` : memberName;
|
|
283
|
+
const typeAnnotation = member.typeAnnotation?.typeAnnotation;
|
|
284
|
+
let type = typeAnnotation ? context.sourceCode.getText(typeAnnotation) : member.value === null ? void 0 : expressionToTypeString(member.value, context, false);
|
|
285
|
+
if (type === void 0) {
|
|
286
|
+
type = "any";
|
|
287
|
+
context.report({
|
|
288
|
+
node: member,
|
|
289
|
+
messageId: "typeAnnotationRequired"
|
|
290
|
+
});
|
|
291
|
+
hadMissingTypeError = true;
|
|
292
|
+
}
|
|
293
|
+
const modifiers = `${member.static ? "static " : ""}${member.accessibility ? `${member.accessibility} ` : ""}${isAbstractProperty ? "abstract " : ""}${member.readonly ? "readonly " : ""}`;
|
|
294
|
+
const optionalText = member.optional ? "?" : "";
|
|
295
|
+
members.push(`${modifiers}${formattedMemberName}${optionalText}: ${type};`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (statement.type === AST_NODE_TYPES.ReturnStatement) {
|
|
299
|
+
const returnText = context.sourceCode.getText(statement);
|
|
300
|
+
const expectedReturnText = `return ${privateClassName} as typeof Public${baseName}${formattedRestTypeArguments} & TBase;`;
|
|
301
|
+
if (returnText.replaceAll(reWhiteSpaceOrComma, "") !== expectedReturnText.replaceAll(reWhiteSpaceOrComma, "")) {
|
|
302
|
+
context.report({
|
|
303
|
+
node: statement,
|
|
304
|
+
messageId: "updateMixinCode",
|
|
305
|
+
fix: (fixer) => fixer.replaceText(statement, expectedReturnText)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const typeConstraint = firstTypeParameter.constraint;
|
|
311
|
+
const typeConstraintText = typeConstraint === void 0 ? "Constructor<Accessor>" : context.sourceCode.getText(typeConstraint);
|
|
312
|
+
if (typeConstraint !== void 0) {
|
|
313
|
+
const typeParameterText = context.sourceCode.getText(firstTypeParameter);
|
|
314
|
+
context.report({
|
|
315
|
+
node: typeConstraint,
|
|
316
|
+
messageId: "updateMixinCode",
|
|
317
|
+
fix: (fixer) => fixer.replaceText(firstTypeParameter, typeParameterText.split("extends")[0])
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (!hadTypedBaseVariable) {
|
|
321
|
+
context.report({
|
|
322
|
+
loc: body.loc.start,
|
|
323
|
+
messageId: "updateMixinCode",
|
|
324
|
+
fix: (fixer) => fixer.insertTextAfterRange(
|
|
325
|
+
[
|
|
326
|
+
context.sourceCode.getIndexFromLoc(body.loc.start) + 1,
|
|
327
|
+
context.sourceCode.getIndexFromLoc(body.loc.start) + 1
|
|
328
|
+
],
|
|
329
|
+
`
|
|
330
|
+
const TypedBase = Base as unknown as ${typeConstraintText};`
|
|
331
|
+
)
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (hadMissingTypeError) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const localRegionStart = `${regionStart} ${baseName}`;
|
|
338
|
+
const abstractString = isAbstract ? "abstract " : "";
|
|
339
|
+
const autoGeneratedCode = `${localRegionStart}
|
|
340
|
+
declare ${abstractString}class Public${baseName}${formattedRestTypeParameters} {
|
|
341
|
+
constructor(...args: any[]);
|
|
342
|
+
${members.map((member) => ` ${member}`).join("\n")}
|
|
343
|
+
}
|
|
344
|
+
${regionEnd}`;
|
|
345
|
+
const regionStartIndex = sourceCode.indexOf(localRegionStart);
|
|
346
|
+
if (regionStartIndex === -1) {
|
|
347
|
+
context.report({
|
|
348
|
+
loc: node.loc.end,
|
|
349
|
+
messageId: "updateMixinCode",
|
|
350
|
+
fix: (fixer) => fixer.insertTextAfter(node, `
|
|
351
|
+
|
|
352
|
+
${autoGeneratedCode}
|
|
353
|
+
`)
|
|
354
|
+
});
|
|
355
|
+
} else {
|
|
356
|
+
let regionEndString = regionEnd;
|
|
357
|
+
let regionEndIndex = sourceCode.indexOf(regionEndString, regionStartIndex);
|
|
358
|
+
if (regionEndIndex === -1) {
|
|
359
|
+
regionEndString = regionEndCrlf;
|
|
360
|
+
regionEndIndex = sourceCode.indexOf(regionEndString, regionStartIndex);
|
|
361
|
+
}
|
|
362
|
+
if (regionEndIndex === -1) {
|
|
363
|
+
regionEndIndex = sourceCode.length;
|
|
364
|
+
} else {
|
|
365
|
+
regionEndIndex += regionEndString.length;
|
|
366
|
+
}
|
|
367
|
+
const oldRegionCode = sourceCode.slice(regionStartIndex, regionEndIndex);
|
|
368
|
+
const oldRegionCodeWithoutWhitespace = oldRegionCode.replaceAll(reWhiteSpaceOrComma, "");
|
|
369
|
+
const newRegionCodeWithoutWhitespace = autoGeneratedCode.replaceAll(reWhiteSpaceOrComma, "");
|
|
370
|
+
if (oldRegionCodeWithoutWhitespace === newRegionCodeWithoutWhitespace) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const line = sourceCode.slice(0, regionStartIndex).split("\n").length;
|
|
374
|
+
context.report({
|
|
375
|
+
// Region start:
|
|
376
|
+
loc: {
|
|
377
|
+
line,
|
|
378
|
+
column: 0
|
|
379
|
+
},
|
|
380
|
+
messageId: "updateMixinCode",
|
|
381
|
+
fix: (fixer) => fixer.replaceTextRange([regionStartIndex, regionEndIndex], autoGeneratedCode)
|
|
382
|
+
});
|
|
383
|
+
}
|
|
129
384
|
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
const reWhiteSpaceOrComma = /[\s,]+/gu;
|
|
389
|
+
const regionStart = "// #region Autogenerated";
|
|
390
|
+
const regionEnd = "// #endregion\n";
|
|
391
|
+
const regionEndCrlf = "// #endregion\r\n";
|
|
392
|
+
function functionToTypeString(definition, context, kind = void 0) {
|
|
393
|
+
let hadMissingTypeError = false;
|
|
394
|
+
const parameters = definition.params.map((param, index) => {
|
|
395
|
+
if (param.type === AST_NODE_TYPES.TSParameterProperty) {
|
|
396
|
+
throw new Error("Unsupported");
|
|
397
|
+
}
|
|
398
|
+
const name = param.type === AST_NODE_TYPES.RestElement && param.argument.type === AST_NODE_TYPES.Identifier ? `...${param.argument.name}` : param.type === AST_NODE_TYPES.Identifier ? param.name : param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.Identifier ? param.left.name : `_arg${index}`;
|
|
399
|
+
const optional = "optional" in param && param.optional || param.type === AST_NODE_TYPES.AssignmentPattern ? "?" : "";
|
|
400
|
+
const typeAnnotation = param.type === AST_NODE_TYPES.AssignmentPattern ? param?.left.typeAnnotation?.typeAnnotation : param.typeAnnotation?.typeAnnotation;
|
|
401
|
+
let type = typeAnnotation === void 0 ? param.type === AST_NODE_TYPES.AssignmentPattern ? expressionToTypeString(param.right, context, false) : void 0 : context.sourceCode.getText(typeAnnotation);
|
|
402
|
+
if (type === void 0) {
|
|
403
|
+
type = "any";
|
|
130
404
|
context.report({
|
|
131
|
-
node:
|
|
132
|
-
messageId: "
|
|
133
|
-
fix: (fixer) => fixer.replaceText(node.source, `"${specifier}.js"`)
|
|
405
|
+
node: param,
|
|
406
|
+
messageId: "typeAnnotationRequired"
|
|
134
407
|
});
|
|
408
|
+
hadMissingTypeError = true;
|
|
135
409
|
}
|
|
136
|
-
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
410
|
+
return `${name}${optional}: ${type}`;
|
|
411
|
+
}).join(", ");
|
|
412
|
+
if (hadMissingTypeError) {
|
|
413
|
+
return;
|
|
140
414
|
}
|
|
141
|
-
|
|
142
|
-
|
|
415
|
+
const returnType = kind === "set" ? "" : definition.returnType?.typeAnnotation === void 0 ? definition.body?.type === AST_NODE_TYPES.BlockStatement && definition.body.body.length === 1 && definition.body.body[0]?.type === AST_NODE_TYPES.ReturnStatement && definition.body.body[0].argument !== null ? expressionToTypeString(definition.body.body[0].argument, context, false) : void 0 : context.sourceCode.getText(definition.returnType.typeAnnotation);
|
|
416
|
+
if (returnType === void 0) {
|
|
417
|
+
context.report({
|
|
418
|
+
node: definition,
|
|
419
|
+
messageId: "typeAnnotationRequired"
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const returnToken = kind === "signature" ? " => " : ": ";
|
|
424
|
+
const formattedReturnType = returnType === "" ? "" : `${returnToken}${returnType}`;
|
|
425
|
+
const typeArguments = definition.typeParameters === void 0 ? "" : context.sourceCode.getText(definition.typeParameters);
|
|
426
|
+
return `${typeArguments}(${parameters})${formattedReturnType}`;
|
|
427
|
+
}
|
|
428
|
+
function expressionToTypeString(expression, context, asConst) {
|
|
429
|
+
const text = context.sourceCode.getText(expression);
|
|
430
|
+
if (text === "undefined") {
|
|
431
|
+
return text;
|
|
432
|
+
}
|
|
433
|
+
if (text === "true" || text === "false") {
|
|
434
|
+
return asConst ? text : "boolean";
|
|
435
|
+
}
|
|
436
|
+
if (expression.type === AST_NODE_TYPES.Literal) {
|
|
437
|
+
return asConst ? typeof expression.value === "string" ? JSON.stringify(expression.value) : expression.value?.toString() ?? "null" : typeof expression.value === "string" ? "string" : typeof expression.value === "number" ? "number" : typeof expression.value === "bigint" ? "bigint" : typeof expression.value === "boolean" ? "boolean" : typeof expression.value === "object" && expression.value !== null ? "RegExp" : "null";
|
|
438
|
+
}
|
|
439
|
+
if (expression.type === AST_NODE_TYPES.UnaryExpression && expression.operator === "-" && expression.argument.type === AST_NODE_TYPES.Literal) {
|
|
440
|
+
const value = expression.argument.value;
|
|
441
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
442
|
+
return asConst ? `-${value}` : "number";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
446
|
+
return "string";
|
|
447
|
+
}
|
|
448
|
+
if (expression.type === AST_NODE_TYPES.TSAsExpression || expression.type === AST_NODE_TYPES.TSTypeAssertion) {
|
|
449
|
+
const typeAnnotation = expression.typeAnnotation;
|
|
450
|
+
if (typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier) {
|
|
451
|
+
if (typeAnnotation.typeName.name === "const") {
|
|
452
|
+
return expressionToTypeString(expression.expression, context, true);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return context.sourceCode.getText(typeAnnotation);
|
|
456
|
+
}
|
|
457
|
+
if (expression.type === AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
458
|
+
const typeAnnotation = expression.typeAnnotation;
|
|
459
|
+
return asConst ? expressionToTypeString(expression.expression, context, true) : context.sourceCode.getText(typeAnnotation);
|
|
460
|
+
}
|
|
461
|
+
if (expression.type === AST_NODE_TYPES.TSNonNullExpression) {
|
|
462
|
+
return expressionToTypeString(expression.expression, context, asConst);
|
|
463
|
+
}
|
|
464
|
+
if (expression.type === AST_NODE_TYPES.NewExpression && expression.callee.type === AST_NODE_TYPES.Identifier) {
|
|
465
|
+
return `${expression.callee.name}${expression.typeArguments ? context.sourceCode.getText(expression.typeArguments) : ""}`;
|
|
466
|
+
}
|
|
467
|
+
if (expression.type === AST_NODE_TYPES.ConditionalExpression) {
|
|
468
|
+
const left = expressionToTypeString(expression.consequent, context, asConst);
|
|
469
|
+
const right = expressionToTypeString(expression.alternate, context, asConst);
|
|
470
|
+
if (left === void 0 || right === void 0) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
return left === right ? left : `${left} | ${right}`;
|
|
474
|
+
}
|
|
475
|
+
if (expression.type === AST_NODE_TYPES.Identifier) {
|
|
476
|
+
return `typeof ${expression.name}`;
|
|
477
|
+
}
|
|
478
|
+
if (expression.type === AST_NODE_TYPES.FunctionExpression || expression.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
479
|
+
return functionToTypeString(expression, context, "signature");
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const description$5 = "Using .d.ts files is discouraged. Prefer .ts files instead, as they are type-checked and not in global scope.";
|
|
143
484
|
const allowedNames = /* @__PURE__ */ new Set(["vite-env.d.ts", "components.d.ts"]);
|
|
144
485
|
plugin.createRule({
|
|
145
486
|
name: "no-dts-files",
|
|
146
487
|
meta: {
|
|
147
488
|
docs: {
|
|
148
|
-
description: description$
|
|
489
|
+
description: description$5,
|
|
149
490
|
defaultLevel: "warn"
|
|
150
491
|
},
|
|
151
492
|
messages: {
|
|
152
|
-
avoidDtsFiles: description$
|
|
493
|
+
avoidDtsFiles: description$5
|
|
153
494
|
},
|
|
154
495
|
type: "suggestion",
|
|
155
496
|
schema: []
|
|
@@ -179,44 +520,315 @@ plugin.createRule({
|
|
|
179
520
|
};
|
|
180
521
|
}
|
|
181
522
|
});
|
|
182
|
-
const description$2 = `Enforce consistent logging so that ArcGIS developers can easily debug errors or warnings logged by our web components, which may lack a meaningful context in compiled code. See [our documentation on @arcgis/toolkit/log](https://webgis.esri.com/references/toolkit/log).`;
|
|
183
523
|
plugin.createRule({
|
|
184
|
-
name: "
|
|
524
|
+
name: "no-empty-catch",
|
|
185
525
|
meta: {
|
|
186
526
|
docs: {
|
|
187
|
-
description:
|
|
527
|
+
description: "Disallow empty catch calls since empty catch calls in a promise chain don't catch any exceptions",
|
|
528
|
+
// Enabled by default because this caught real bugs and has autofix
|
|
529
|
+
defaultLevel: "error"
|
|
530
|
+
},
|
|
531
|
+
messages: { emptyCatch: "no empty catch calls" },
|
|
532
|
+
schema: [],
|
|
533
|
+
type: "problem",
|
|
534
|
+
fixable: "code"
|
|
535
|
+
},
|
|
536
|
+
defaultOptions: [],
|
|
537
|
+
create(context) {
|
|
538
|
+
return {
|
|
539
|
+
"CallExpression[callee.property.name='catch'][arguments.length=0]"(node) {
|
|
540
|
+
const [, rangeEnd] = node.range;
|
|
541
|
+
context.report({
|
|
542
|
+
node,
|
|
543
|
+
messageId: "emptyCatch",
|
|
544
|
+
fix: (fixer) => fixer.insertTextBeforeRange([rangeEnd - 1, rangeEnd], "() => {}")
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
const description$4 = `Imports of files outside the src/ folder are not-portable and likely to break for consumers of this package.`;
|
|
551
|
+
plugin.createRule({
|
|
552
|
+
name: "no-import-outside-src",
|
|
553
|
+
meta: {
|
|
554
|
+
docs: {
|
|
555
|
+
description: description$4,
|
|
556
|
+
defaultLevel: "error"
|
|
557
|
+
},
|
|
558
|
+
messages: {
|
|
559
|
+
noImportOutsideSrc: description$4
|
|
560
|
+
},
|
|
561
|
+
type: "problem",
|
|
562
|
+
schema: []
|
|
563
|
+
},
|
|
564
|
+
defaultOptions: [],
|
|
565
|
+
create(context) {
|
|
566
|
+
const fileName = context.filename;
|
|
567
|
+
const srcIndex = fileName.indexOf("/src/");
|
|
568
|
+
if (srcIndex === -1) {
|
|
569
|
+
return {};
|
|
570
|
+
}
|
|
571
|
+
const basePath = fileName.slice(0, srcIndex + "/src/".length);
|
|
572
|
+
if (isTestFile(fileName) || isStorybookFile(fileName)) {
|
|
573
|
+
return {};
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
ImportDeclaration(node) {
|
|
577
|
+
const specifier = node.source.value;
|
|
578
|
+
const isRelativeAncestor = specifier.startsWith("../");
|
|
579
|
+
if (!isRelativeAncestor) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const relativePath = resolve(fileName, "..", specifier);
|
|
583
|
+
if (!relativePath.startsWith(basePath) && !relativePath.endsWith(".json")) {
|
|
584
|
+
context.report({
|
|
585
|
+
messageId: "noImportOutsideSrc",
|
|
586
|
+
node: node.source
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
plugin.createRule({
|
|
594
|
+
name: "no-instanceof",
|
|
595
|
+
meta: {
|
|
596
|
+
docs: {
|
|
597
|
+
description: "Disallow `instanceof` outside an allowlist.",
|
|
598
|
+
// Disabling by default because this matters for
|
|
599
|
+
// lower-level libraries more than higher-level libraries or applications.
|
|
600
|
+
// Also, many false-positives.
|
|
188
601
|
defaultLevel: "off"
|
|
189
|
-
// NOTE: this is turned on conditionally in root eslint config
|
|
190
602
|
},
|
|
603
|
+
schema: [
|
|
604
|
+
{
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: {
|
|
607
|
+
allow: { type: "array", items: { type: "string" }, uniqueItems: true }
|
|
608
|
+
},
|
|
609
|
+
additionalProperties: false
|
|
610
|
+
}
|
|
611
|
+
],
|
|
191
612
|
messages: {
|
|
192
|
-
|
|
613
|
+
instanceOf: "Avoid `instanceof` because it statically pulls the module defining the constructor into the bundle. Instead, use a Symbol together with a type guard function."
|
|
614
|
+
},
|
|
615
|
+
type: "problem"
|
|
616
|
+
},
|
|
617
|
+
defaultOptions: [{}],
|
|
618
|
+
create(context) {
|
|
619
|
+
const options = normalizeOptions(context.options[0]);
|
|
620
|
+
const sourceCode = context.sourceCode;
|
|
621
|
+
return {
|
|
622
|
+
"BinaryExpression[operator='instanceof']"(node) {
|
|
623
|
+
const right = node.right;
|
|
624
|
+
if (right.type === AST_NODE_TYPES.ThisExpression) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (right.type !== AST_NODE_TYPES.Identifier) {
|
|
628
|
+
context.report({ node: right, messageId: "instanceOf" });
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const scope = sourceCode.getScope(node);
|
|
632
|
+
if (!isAllowedIdentifier(right.name, scope, options)) {
|
|
633
|
+
context.report({ node: right, messageId: "instanceOf" });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
function normalizeOptions(options) {
|
|
640
|
+
return {
|
|
641
|
+
allowSet: new Set(options?.allow ?? [])
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function resolveVariable(scope, name) {
|
|
645
|
+
for (let current = scope; current; current = current.upper) {
|
|
646
|
+
const variable = current.variables.find((entry) => entry.name === name);
|
|
647
|
+
if (variable) {
|
|
648
|
+
return variable;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const builtInTypes = new Set(Object.keys({ ...globals.browser, ...globals.builtin }));
|
|
654
|
+
function isAllowedIdentifier(name, scope, options) {
|
|
655
|
+
if (options.allowSet.has(name)) {
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
const variable = resolveVariable(scope, name);
|
|
659
|
+
if (builtInTypes.has(name) && isUnshadowedBuiltIn(variable)) {
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
return isClassVariable(variable);
|
|
663
|
+
}
|
|
664
|
+
function isUnshadowedBuiltIn(variable) {
|
|
665
|
+
if (!variable) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
return variable.scope.type === TSESLint.Scope.ScopeType.global && variable.defs.length === 0;
|
|
669
|
+
}
|
|
670
|
+
function isClassVariable(variable) {
|
|
671
|
+
return variable?.defs.some((definition) => isClassDefinition(definition)) ?? false;
|
|
672
|
+
}
|
|
673
|
+
function isClassDefinition(definition) {
|
|
674
|
+
const definitionType = definition.type;
|
|
675
|
+
if (definitionType === TSESLint.Scope.DefinitionType.ClassName) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
if (definitionType !== TSESLint.Scope.DefinitionType.Variable) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
const variableNode = definition.node;
|
|
682
|
+
if (variableNode?.type !== AST_NODE_TYPES.VariableDeclarator) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return variableNode.init?.type === AST_NODE_TYPES.ClassExpression;
|
|
686
|
+
}
|
|
687
|
+
plugin.createRule({
|
|
688
|
+
name: "no-kebab-case-props",
|
|
689
|
+
meta: {
|
|
690
|
+
docs: {
|
|
691
|
+
description: "Disallow kebab-case props on custom JSX elements.",
|
|
692
|
+
// Off by default till
|
|
693
|
+
// https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/5364#issuecomment-6321339
|
|
694
|
+
// is resolved
|
|
695
|
+
defaultLevel: "off"
|
|
696
|
+
},
|
|
697
|
+
messages: {
|
|
698
|
+
kebab: "JSX props should use camelCase instead of kebab-case, except for data- or aria- attributes"
|
|
193
699
|
},
|
|
194
700
|
schema: [],
|
|
195
|
-
|
|
196
|
-
|
|
701
|
+
type: "problem",
|
|
702
|
+
fixable: "code"
|
|
197
703
|
},
|
|
198
704
|
defaultOptions: [],
|
|
199
705
|
create(context) {
|
|
200
|
-
|
|
706
|
+
return {
|
|
707
|
+
// looks for JSXAttributes that have a hyphen in their name
|
|
708
|
+
"JSXAttribute[name.name=/-/]"(node) {
|
|
709
|
+
const attrName = node.name.type === AST_NODE_TYPES.JSXIdentifier ? node.name.name : null;
|
|
710
|
+
if (!attrName || attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (node.parent.name.type === AST_NODE_TYPES.JSXIdentifier) {
|
|
714
|
+
const tagName = node.parent.name.name;
|
|
715
|
+
if (isNativeTagName(tagName)) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
context.report({
|
|
720
|
+
node,
|
|
721
|
+
messageId: "kebab",
|
|
722
|
+
fix: (fixer) => fixer.replaceText(node.name, uncapitalize(kebabToPascal(attrName)))
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
function isNativeTagName(tagName) {
|
|
729
|
+
const normalizedTagName = tagName.toLowerCase();
|
|
730
|
+
const isCapitalized = /^[A-Z]/u.test(tagName);
|
|
731
|
+
if (isCapitalized) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
return !normalizedTagName.includes("-") || blockListedCustomElementNames.has(normalizedTagName);
|
|
735
|
+
}
|
|
736
|
+
const blockListedCustomElementNames = /* @__PURE__ */ new Set([
|
|
737
|
+
"annotation-xml",
|
|
738
|
+
"color-profile",
|
|
739
|
+
"font-face",
|
|
740
|
+
"font-face-src",
|
|
741
|
+
"font-face-uri",
|
|
742
|
+
"font-face-format",
|
|
743
|
+
"font-face-name",
|
|
744
|
+
"missing-glyph"
|
|
745
|
+
]);
|
|
746
|
+
const description$3 = "In Storybook render functions, let `args` infer from `Meta<T>` or `StoryObj<T>` instead of annotating the render parameter.";
|
|
747
|
+
plugin.createRule({
|
|
748
|
+
name: "no-story-render-args-type-annotation",
|
|
749
|
+
meta: {
|
|
750
|
+
docs: {
|
|
751
|
+
description: description$3,
|
|
752
|
+
defaultLevel: "off"
|
|
753
|
+
},
|
|
754
|
+
messages: {
|
|
755
|
+
noStoryRenderArgsTypeAnnotation: "Do not annotate `render(args)` in stories. Type the story with `Meta<T>` or `StoryObj<T>` and let `args` infer from that."
|
|
756
|
+
},
|
|
757
|
+
schema: [],
|
|
758
|
+
type: "suggestion",
|
|
759
|
+
fixable: "code"
|
|
760
|
+
},
|
|
761
|
+
defaultOptions: [],
|
|
762
|
+
create(context) {
|
|
763
|
+
if (!isStorybookFile(context.filename)) {
|
|
201
764
|
return {};
|
|
202
765
|
}
|
|
203
766
|
return {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
767
|
+
Property(node) {
|
|
768
|
+
if (!isRenderProperty(node)) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const [firstParameter] = node.value.params;
|
|
772
|
+
if (firstParameter?.type !== AST_NODE_TYPES.Identifier || firstParameter.typeAnnotation === void 0) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
context.report({
|
|
776
|
+
node: firstParameter.typeAnnotation,
|
|
777
|
+
messageId: "noStoryRenderArgsTypeAnnotation",
|
|
778
|
+
fix: (fixer) => fixer.remove(firstParameter.typeAnnotation)
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
function isRenderProperty(node) {
|
|
785
|
+
return node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "render" && (node.value.type === AST_NODE_TYPES.ArrowFunctionExpression || node.value.type === AST_NODE_TYPES.FunctionExpression);
|
|
786
|
+
}
|
|
787
|
+
const description$2 = `Having two JSDoc comments next to each other is most likely a mistake - consider combining them into one, or separating them for clarity.`;
|
|
788
|
+
plugin.createRule({
|
|
789
|
+
name: "no-touching-jsdoc",
|
|
790
|
+
meta: {
|
|
791
|
+
docs: {
|
|
792
|
+
description: description$2,
|
|
793
|
+
defaultLevel: "warn"
|
|
794
|
+
},
|
|
795
|
+
messages: {
|
|
796
|
+
noTouchingJsDoc: description$2
|
|
797
|
+
},
|
|
798
|
+
type: "problem",
|
|
799
|
+
schema: []
|
|
800
|
+
},
|
|
801
|
+
defaultOptions: [],
|
|
802
|
+
create(context) {
|
|
803
|
+
return {
|
|
804
|
+
Program() {
|
|
805
|
+
Array.from(context.sourceCode.text.matchAll(reTouchingJsDoc), (match) => {
|
|
806
|
+
const previousJsDocStart = context.sourceCode.text.slice(0, match.index).lastIndexOf("/**");
|
|
807
|
+
if (previousJsDocStart === -1) {
|
|
808
|
+
return void 0;
|
|
809
|
+
}
|
|
810
|
+
const previousJsDocContent = context.sourceCode.text.slice(
|
|
811
|
+
previousJsDocStart + "/**".length,
|
|
812
|
+
match.index - "*/".length
|
|
813
|
+
);
|
|
814
|
+
const containsOtherComments = previousJsDocContent.includes("*/");
|
|
815
|
+
if (containsOtherComments) {
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
208
818
|
context.report({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
819
|
+
messageId: "noTouchingJsDoc",
|
|
820
|
+
loc: {
|
|
821
|
+
start: context.sourceCode.getLocFromIndex(match.index),
|
|
822
|
+
end: context.sourceCode.getLocFromIndex(match.index + match[0].length)
|
|
213
823
|
}
|
|
214
824
|
});
|
|
215
|
-
|
|
825
|
+
return void 0;
|
|
826
|
+
});
|
|
216
827
|
}
|
|
217
828
|
};
|
|
218
829
|
}
|
|
219
830
|
});
|
|
831
|
+
const reTouchingJsDoc = /\*\/\s+\/\*\*/gu;
|
|
220
832
|
const description$1 = `Do not use links like [](#something) as they are not portable across .d.ts, and are not validated at build time. Use {@link } syntax, or absolute links. See https://webgis.esri.com/references/api-extractor/tags-reference#link)`;
|
|
221
833
|
plugin.createRule({
|
|
222
834
|
name: "no-unsafe-hash-links",
|
|
@@ -272,47 +884,111 @@ function computeReplacement(declareElementsInterface, anchor) {
|
|
|
272
884
|
const tagName = declaration.key.value;
|
|
273
885
|
return `components/${tagName}#${anchor}`;
|
|
274
886
|
}
|
|
275
|
-
const description = "In Storybook render functions, let `args` infer from `Meta<T>` or `StoryObj<T>` instead of annotating the render parameter.";
|
|
276
887
|
plugin.createRule({
|
|
277
|
-
name: "
|
|
888
|
+
name: "require-await-for-async-screenshot-assert",
|
|
278
889
|
meta: {
|
|
279
890
|
docs: {
|
|
280
|
-
description,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
messages: {
|
|
284
|
-
noStoryRenderArgsTypeAnnotation: "Do not annotate `render(args)` in stories. Type the story with `Meta<T>` or `StoryObj<T>` and let `args` infer from that."
|
|
891
|
+
description: "Async tests must await a screenshot assertion.",
|
|
892
|
+
// Enabled by default because it targets a specific pattern and has autofix
|
|
893
|
+
defaultLevel: "error"
|
|
285
894
|
},
|
|
895
|
+
messages: { asyncAssert: "Async tests must await a screenshot assertion." },
|
|
286
896
|
schema: [],
|
|
287
|
-
type: "
|
|
897
|
+
type: "problem",
|
|
288
898
|
fixable: "code"
|
|
289
899
|
},
|
|
290
900
|
defaultOptions: [],
|
|
291
901
|
create(context) {
|
|
292
|
-
if (!
|
|
902
|
+
if (!isTestFile(context.filename)) {
|
|
293
903
|
return {};
|
|
294
904
|
}
|
|
295
905
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const [firstParameter] = node.value.params;
|
|
301
|
-
if (firstParameter?.type !== AST_NODE_TYPES.Identifier || firstParameter.typeAnnotation === void 0) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
906
|
+
"CallExpression[callee.name='test'] > ArrowFunctionExpression[async] ExpressionStatement > CallExpression[callee.object.name='assert'][callee.property.name='screenshot']"(node) {
|
|
907
|
+
const [rangeStart] = node.range;
|
|
304
908
|
context.report({
|
|
305
|
-
node
|
|
306
|
-
messageId: "
|
|
307
|
-
fix: (fixer) => fixer.
|
|
909
|
+
node,
|
|
910
|
+
messageId: "asyncAssert",
|
|
911
|
+
fix: (fixer) => fixer.insertTextBeforeRange([rangeStart - 1, rangeStart], " await")
|
|
308
912
|
});
|
|
309
913
|
}
|
|
310
914
|
};
|
|
311
915
|
}
|
|
312
916
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
917
|
+
plugin.createRule({
|
|
918
|
+
name: "require-button-type",
|
|
919
|
+
meta: {
|
|
920
|
+
docs: {
|
|
921
|
+
description: "Prevent usage of button elements without an explicit type attribute.",
|
|
922
|
+
// Disabling by default for now because it found many cases (that
|
|
923
|
+
// presumably work correctly today) so enabling it is a bit too disruptive.
|
|
924
|
+
// We should coordinate enabling it with enabling other lint rules to
|
|
925
|
+
// announce at once.
|
|
926
|
+
defaultLevel: "off"
|
|
927
|
+
},
|
|
928
|
+
messages: { missingType: "An explicit type attribute is required on buttons" },
|
|
929
|
+
schema: [],
|
|
930
|
+
type: "problem",
|
|
931
|
+
fixable: "code"
|
|
932
|
+
},
|
|
933
|
+
defaultOptions: [],
|
|
934
|
+
create(context) {
|
|
935
|
+
return {
|
|
936
|
+
"JSXOpeningElement[name.name='button']"(node) {
|
|
937
|
+
if (!node.attributes.some((attr) => attr.type === AST_NODE_TYPES.JSXAttribute && attr.name.name === "type")) {
|
|
938
|
+
context.report({
|
|
939
|
+
node,
|
|
940
|
+
messageId: "missingType",
|
|
941
|
+
fix: (fixer) => fixer.insertTextAfter(node.name, ' type="button"')
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
const description = `Imports of certain packages need to end with .js for better compatibility with CDN and bundlers.`;
|
|
949
|
+
const packagesToEnforce = ["@arcgis/core/", "@amcharts/amcharts4/", "@amcharts/amcharts5/", "@vaadin/grid/"];
|
|
950
|
+
plugin.createRule({
|
|
951
|
+
name: "require-js-in-imports",
|
|
952
|
+
meta: {
|
|
953
|
+
docs: {
|
|
954
|
+
description,
|
|
955
|
+
defaultLevel: "warn"
|
|
956
|
+
},
|
|
957
|
+
messages: {
|
|
958
|
+
requireJsInImports: description
|
|
959
|
+
},
|
|
960
|
+
type: "problem",
|
|
961
|
+
fixable: "code",
|
|
962
|
+
schema: []
|
|
963
|
+
},
|
|
964
|
+
defaultOptions: [],
|
|
965
|
+
create(context) {
|
|
966
|
+
if (isTestFile(context.filename)) {
|
|
967
|
+
return {};
|
|
968
|
+
}
|
|
969
|
+
function updateSpecifier(node) {
|
|
970
|
+
if (node.source.type !== AST_NODE_TYPES.Literal) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const specifier = node.source.value;
|
|
974
|
+
const lastSlashIndex = specifier?.toString().lastIndexOf("/") ?? 0;
|
|
975
|
+
const dotSearchIndex = lastSlashIndex === -1 ? 0 : lastSlashIndex + 1;
|
|
976
|
+
if (typeof specifier !== "string" || !packagesToEnforce.some((pkg) => specifier.startsWith(pkg)) || // Already ends with .js or .json
|
|
977
|
+
specifier.includes(".", dotSearchIndex)) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
context.report({
|
|
981
|
+
node: node.source,
|
|
982
|
+
messageId: "requireJsInImports",
|
|
983
|
+
fix: (fixer) => fixer.replaceText(node.source, `"${specifier}.js"`)
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
return {
|
|
987
|
+
ImportDeclaration: updateSpecifier,
|
|
988
|
+
ImportExpression: updateSpecifier
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
});
|
|
316
992
|
const webgisPlugin = plugin.finalize();
|
|
317
993
|
export {
|
|
318
994
|
webgisPlugin
|