@arcgis/eslint-config 4.32.0-next.67
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/LICENSE.md +13 -0
- package/README.md +21 -0
- package/dist/chunk-25YPAQ6A.js +64 -0
- package/dist/chunk-C2D6KHIP.js +24 -0
- package/dist/chunk-D7HD47IC.js +974 -0
- package/dist/chunk-WAWBOPY7.js +19 -0
- package/dist/config/extra.d.ts +24 -0
- package/dist/config/extra.js +36 -0
- package/dist/config/index.d.ts +22 -0
- package/dist/config/index.js +588 -0
- package/dist/config/lumina.d.ts +7 -0
- package/dist/config/lumina.js +220 -0
- package/dist/config/restrictedImports.d.ts +6 -0
- package/dist/config/restrictedImports.js +6 -0
- package/dist/config/storybook.d.ts +2 -0
- package/dist/plugins/lumina/index.d.ts +6 -0
- package/dist/plugins/lumina/index.js +7 -0
- package/dist/plugins/lumina/rules/add-missing-jsx-import.d.ts +3 -0
- package/dist/plugins/lumina/rules/add-missing-jsx-import.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/auto-add-type.d.ts +4 -0
- package/dist/plugins/lumina/rules/auto-add-type.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/component-placement-rules.d.ts +3 -0
- package/dist/plugins/lumina/rules/component-placement-rules.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/consistent-event-naming.d.ts +17 -0
- package/dist/plugins/lumina/rules/consistent-event-naming.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/decorators-context.d.ts +3 -0
- package/dist/plugins/lumina/rules/decorators-context.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-ignore-jsdoc-tag.d.ts +3 -0
- package/dist/plugins/lumina/rules/no-ignore-jsdoc-tag.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-incorrect-dynamic-tag-name.d.ts +4 -0
- package/dist/plugins/lumina/rules/no-incorrect-dynamic-tag-name.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-inline-arrow-in-ref.d.ts +3 -0
- package/dist/plugins/lumina/rules/no-inline-arrow-in-ref.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-listen-in-connected-callback.d.ts +3 -0
- package/dist/plugins/lumina/rules/no-listen-in-connected-callback.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-needless-bind-this.d.ts +3 -0
- package/dist/plugins/lumina/rules/no-needless-bind-this.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-property-name-start-with-on.d.ts +3 -0
- package/dist/plugins/lumina/rules/no-property-name-start-with-on.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/no-render-false.d.ts +4 -0
- package/dist/plugins/lumina/rules/no-render-false.test.d.ts +1 -0
- package/dist/plugins/lumina/rules/tag-name-rules.d.ts +10 -0
- package/dist/plugins/lumina/rules/tag-name-rules.spec.d.ts +1 -0
- package/dist/plugins/lumina/utils/checker.d.ts +2 -0
- package/dist/plugins/lumina/utils/creator.d.ts +6 -0
- package/dist/plugins/lumina/utils/estree.d.ts +14 -0
- package/dist/plugins/utils/makePlugin.d.ts +14 -0
- package/dist/plugins/utils/tests.d.ts +26 -0
- package/dist/plugins/webgis/index.d.ts +6 -0
- package/dist/plugins/webgis/index.js +7 -0
- package/dist/plugins/webgis/rules/no-import-outside-src.d.ts +3 -0
- package/dist/plugins/webgis/rules/no-import-outside-src.test.d.ts +1 -0
- package/dist/plugins/webgis/utils/creator.d.ts +6 -0
- package/package.json +37 -0
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeEslintPlugin
|
|
3
|
+
} from "./chunk-C2D6KHIP.js";
|
|
4
|
+
|
|
5
|
+
// src/plugins/lumina/rules/add-missing-jsx-import.ts
|
|
6
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
|
|
7
|
+
|
|
8
|
+
// src/plugins/lumina/utils/creator.ts
|
|
9
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
10
|
+
var createRule = ESLintUtils.RuleCreator(
|
|
11
|
+
(rule) => `https://devtopia.esri.com/WebGIS/arcgis-web-components/tree/main/packages/support-packages/eslint-config/src/plugins/lumina/rules/${rule}.ts`
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// src/plugins/lumina/utils/estree.ts
|
|
15
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
16
|
+
var unwrapExpression = (expression) => expression.type === AST_NODE_TYPES.TSAsExpression || expression.type === AST_NODE_TYPES.TSNonNullExpression || expression.type === AST_NODE_TYPES.TSSatisfiesExpression ? unwrapExpression(expression.expression) : expression;
|
|
17
|
+
var luminaEntrypointName = "@arcgis/lumina";
|
|
18
|
+
var luminaJsxExportName = "h";
|
|
19
|
+
function checkForLuminaJsx() {
|
|
20
|
+
const ImportDeclaration = (node) => {
|
|
21
|
+
if (node.source.value !== luminaEntrypointName) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const specifier of node.specifiers) {
|
|
25
|
+
if (specifier.type === AST_NODE_TYPES.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES.Identifier && specifier.imported.name === luminaJsxExportName || specifier.imported.type === AST_NODE_TYPES.Literal && specifier.imported.value === luminaJsxExportName)) {
|
|
26
|
+
withProperty.isLuminaJsx = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const withProperty = ImportDeclaration;
|
|
32
|
+
return withProperty;
|
|
33
|
+
}
|
|
34
|
+
function hasDecorator(node, decoratorName) {
|
|
35
|
+
return node.decorators.some(
|
|
36
|
+
(decorator) => decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === decoratorName
|
|
37
|
+
) ?? false;
|
|
38
|
+
}
|
|
39
|
+
function extractDeclareElementsInterface(node) {
|
|
40
|
+
return node.kind === "global" ? node.body.body.find(
|
|
41
|
+
(node2) => node2.type === AST_NODE_TYPES.TSInterfaceDeclaration && node2.id.name === "DeclareElements"
|
|
42
|
+
) : void 0;
|
|
43
|
+
}
|
|
44
|
+
function isCreateEvent(node) {
|
|
45
|
+
return node.value?.type === AST_NODE_TYPES.CallExpression && node.value.callee.type === AST_NODE_TYPES.Identifier && node.value.callee.name === "createEvent" && !node.static;
|
|
46
|
+
}
|
|
47
|
+
var getProperty = (properties, name) => properties?.find(
|
|
48
|
+
(option) => option.type === AST_NODE_TYPES.Property && option.key.type === AST_NODE_TYPES.Identifier && option.key.name === name
|
|
49
|
+
)?.value;
|
|
50
|
+
function isGetterWithoutSetter(node) {
|
|
51
|
+
const isGetter = node.type === AST_NODE_TYPES.MethodDefinition && node.kind === "get";
|
|
52
|
+
if (!isGetter) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const index = node.parent.body.indexOf(node);
|
|
56
|
+
const previousNode = node.parent.body.at(index - 1);
|
|
57
|
+
const nextNode = node.parent.body.at(index + 1);
|
|
58
|
+
const name = getName(node);
|
|
59
|
+
if (name === void 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const previousIsSetter = previousNode?.type === AST_NODE_TYPES.MethodDefinition && previousNode.kind === "set" && getName(previousNode) === name;
|
|
63
|
+
if (previousIsSetter) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const nextIsSetter = nextNode?.type === AST_NODE_TYPES.MethodDefinition && nextNode.kind === "set" && getName(nextNode) === name;
|
|
67
|
+
return !nextIsSetter;
|
|
68
|
+
}
|
|
69
|
+
function getName(node) {
|
|
70
|
+
if (node.key.type === AST_NODE_TYPES.Identifier) {
|
|
71
|
+
return node.key.name;
|
|
72
|
+
} else if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === "string") {
|
|
73
|
+
return node.key.value;
|
|
74
|
+
} else {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/plugins/lumina/rules/add-missing-jsx-import.ts
|
|
80
|
+
var importDeclaration = `import { ${luminaJsxExportName} } from "${luminaEntrypointName}";`;
|
|
81
|
+
var description = `To use Lumina's JSX, you need to ${importDeclaration}`;
|
|
82
|
+
var addMissingJsxImport = createRule({
|
|
83
|
+
name: "add-missing-jsx-import",
|
|
84
|
+
meta: {
|
|
85
|
+
docs: {
|
|
86
|
+
description
|
|
87
|
+
},
|
|
88
|
+
messages: {
|
|
89
|
+
addMissingJsxImport: description
|
|
90
|
+
},
|
|
91
|
+
type: "problem",
|
|
92
|
+
schema: [],
|
|
93
|
+
fixable: "code"
|
|
94
|
+
},
|
|
95
|
+
defaultOptions: [],
|
|
96
|
+
create(context) {
|
|
97
|
+
let errorAlreadyReported = false;
|
|
98
|
+
let isUsingLumina = false;
|
|
99
|
+
let lastImportDeclaration;
|
|
100
|
+
let lastLuminaImportClause;
|
|
101
|
+
let hasLuminaJsxImport = false;
|
|
102
|
+
return {
|
|
103
|
+
ImportDeclaration(node) {
|
|
104
|
+
lastImportDeclaration = node;
|
|
105
|
+
if (node.source.value !== luminaEntrypointName) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
isUsingLumina = true;
|
|
109
|
+
const isTypeOnly = node.importKind === "type";
|
|
110
|
+
for (const specifier of node.specifiers) {
|
|
111
|
+
if (!isTypeOnly) {
|
|
112
|
+
lastLuminaImportClause = specifier;
|
|
113
|
+
}
|
|
114
|
+
if (specifier.type === AST_NODE_TYPES2.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES2.Identifier && specifier.imported.name === luminaJsxExportName || specifier.imported.type === AST_NODE_TYPES2.Literal && specifier.imported.value === luminaJsxExportName)) {
|
|
115
|
+
hasLuminaJsxImport = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
JSXIdentifier(node) {
|
|
121
|
+
if (isUsingLumina && !hasLuminaJsxImport && !errorAlreadyReported) {
|
|
122
|
+
errorAlreadyReported = true;
|
|
123
|
+
context.report({
|
|
124
|
+
messageId: "addMissingJsxImport",
|
|
125
|
+
node,
|
|
126
|
+
fix(fixer) {
|
|
127
|
+
if (lastLuminaImportClause !== void 0) {
|
|
128
|
+
return fixer.insertTextAfter(lastLuminaImportClause, `, ${luminaJsxExportName}`);
|
|
129
|
+
}
|
|
130
|
+
if (lastImportDeclaration !== void 0) {
|
|
131
|
+
return fixer.insertTextAfter(lastImportDeclaration, importDeclaration);
|
|
132
|
+
} else {
|
|
133
|
+
return fixer.insertTextBefore(context.sourceCode.ast, importDeclaration);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/plugins/lumina/rules/auto-add-type.ts
|
|
144
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES3, ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
|
|
145
|
+
import ts from "typescript";
|
|
146
|
+
var description2 = "Auto add { type: Boolean } or { type: Number } where necessary";
|
|
147
|
+
var autoAddType = createRule({
|
|
148
|
+
name: "auto-add-type",
|
|
149
|
+
meta: {
|
|
150
|
+
docs: {
|
|
151
|
+
description: description2
|
|
152
|
+
},
|
|
153
|
+
messages: {
|
|
154
|
+
addType: `This property is of {{ type }} type, yet the type is not trivially inferrable from the AST without type-checking. Such properties require a { type: {{ type }} } annotation.
|
|
155
|
+
|
|
156
|
+
More information: https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/1991`
|
|
157
|
+
},
|
|
158
|
+
type: "problem",
|
|
159
|
+
schema: [],
|
|
160
|
+
fixable: "code"
|
|
161
|
+
},
|
|
162
|
+
defaultOptions: [],
|
|
163
|
+
create(context) {
|
|
164
|
+
const services = ESLintUtils2.getParserServices(context);
|
|
165
|
+
function checkProperty(node) {
|
|
166
|
+
const propertyDecorator = node.decorators.find(
|
|
167
|
+
(decorator) => decorator.expression.type === AST_NODE_TYPES3.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES3.Identifier && decorator.expression.callee.name === "property"
|
|
168
|
+
);
|
|
169
|
+
const callExpression = propertyDecorator?.expression.type === AST_NODE_TYPES3.CallExpression ? propertyDecorator.expression : void 0;
|
|
170
|
+
if (callExpression === void 0) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const trivialType = inferTrivialType(node);
|
|
174
|
+
if (trivialType === "Number" || trivialType === "Boolean") {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const options = callExpression.arguments[0]?.type === AST_NODE_TYPES3.ObjectExpression ? callExpression.arguments[0] : void 0;
|
|
178
|
+
const properties = options?.properties;
|
|
179
|
+
const attributeOption = getProperty(properties, "attribute");
|
|
180
|
+
const isAttributeFalse = attributeOption?.type === AST_NODE_TYPES3.Literal && attributeOption.value === false;
|
|
181
|
+
if (isAttributeFalse) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const typeProperty = getProperty(properties, "type");
|
|
185
|
+
if (typeProperty !== void 0) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const type = services.getTypeAtLocation(node);
|
|
189
|
+
const typeFlags = (type.flags & ts.TypeFlags.Union ? type.types : [type]).reduce(
|
|
190
|
+
(flags, type2) => flags | type2.flags,
|
|
191
|
+
0
|
|
192
|
+
);
|
|
193
|
+
const isNotCastable = typeFlags & (ts.TypeFlags.String | ts.TypeFlags.Any | ts.TypeFlags.Unknown);
|
|
194
|
+
if (isNotCastable) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const isNumberType = typeFlags & ts.TypeFlags.NumberLike;
|
|
198
|
+
const isBooleanType = typeFlags & ts.TypeFlags.BooleanLike;
|
|
199
|
+
if (!isNumberType && !isBooleanType) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (isNumberType && isBooleanType) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const comments = context.sourceCode.getCommentsBefore(node);
|
|
206
|
+
const isDocsOnlyReadOnly = comments.some((comment) => comment.value.includes("@readonly"));
|
|
207
|
+
const readOnlyProperty = getProperty(properties, "readOnly");
|
|
208
|
+
const hasReadOnlyFlag = readOnlyProperty?.type === AST_NODE_TYPES3.Literal && readOnlyProperty.value === true;
|
|
209
|
+
const isReadOnly = isDocsOnlyReadOnly || hasReadOnlyFlag || isGetterWithoutSetter(node);
|
|
210
|
+
const reflectsProperty = getProperty(properties, "reflects");
|
|
211
|
+
if (isReadOnly && (!isBooleanType || reflectsProperty?.type !== AST_NODE_TYPES3.Literal || reflectsProperty.value !== true)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
context.report({
|
|
215
|
+
messageId: "addType",
|
|
216
|
+
data: {
|
|
217
|
+
type: isNumberType ? "Number" : "Boolean"
|
|
218
|
+
},
|
|
219
|
+
node,
|
|
220
|
+
fix(fixer) {
|
|
221
|
+
const sourceCode = context.sourceCode;
|
|
222
|
+
if (options === void 0) {
|
|
223
|
+
const token = sourceCode.getTokenAfter(callExpression.callee, {
|
|
224
|
+
filter: (token2) => token2.value === "("
|
|
225
|
+
});
|
|
226
|
+
return fixer.insertTextAfterRange(token.range, `{ type: ${isNumberType ? "Number" : "Boolean"} }`);
|
|
227
|
+
} else if (options.properties.length === 0) {
|
|
228
|
+
return fixer.replaceTextRange(options.range, `{ type: ${isNumberType ? "Number" : "Boolean"} }`);
|
|
229
|
+
} else {
|
|
230
|
+
return fixer.insertTextBefore(options.properties[0], `type: ${isNumberType ? "Number" : "Boolean"}, `);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
PropertyDefinition(node) {
|
|
237
|
+
checkProperty(node);
|
|
238
|
+
},
|
|
239
|
+
MethodDefinition(node) {
|
|
240
|
+
checkProperty(node);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
function inferTrivialType(member) {
|
|
246
|
+
const type = member.type === AST_NODE_TYPES3.PropertyDefinition ? member.typeAnnotation?.typeAnnotation : member.type === AST_NODE_TYPES3.MethodDefinition && member.kind === "set" ? member.value.params[0]?.typeAnnotation?.typeAnnotation : member.type === AST_NODE_TYPES3.MethodDefinition && member.kind === "get" ? member.value.returnType?.typeAnnotation : void 0;
|
|
247
|
+
if (type === void 0) {
|
|
248
|
+
if (member.value?.type === AST_NODE_TYPES3.Literal && typeof member.value.value === "number" || member.value?.type === AST_NODE_TYPES3.UnaryExpression && member.value.argument.type === AST_NODE_TYPES3.Literal && typeof member.value.argument.value === "number" && member.value.operator === "-") {
|
|
249
|
+
return "Number";
|
|
250
|
+
}
|
|
251
|
+
if (member.value?.type === AST_NODE_TYPES3.Literal && typeof member.value.value === "boolean") {
|
|
252
|
+
return "Boolean";
|
|
253
|
+
}
|
|
254
|
+
} else if (type.type === AST_NODE_TYPES3.TSNumberKeyword) {
|
|
255
|
+
return "Number";
|
|
256
|
+
} else if (type.type === AST_NODE_TYPES3.TSBooleanKeyword) {
|
|
257
|
+
return "Boolean";
|
|
258
|
+
}
|
|
259
|
+
return "Other";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/plugins/lumina/rules/component-placement-rules.ts
|
|
263
|
+
var description3 = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
|
|
264
|
+
var componentPlacementRules = createRule({
|
|
265
|
+
name: "component-placement-rules",
|
|
266
|
+
meta: {
|
|
267
|
+
docs: {
|
|
268
|
+
description: description3
|
|
269
|
+
},
|
|
270
|
+
messages: {
|
|
271
|
+
fileFolderNameMismatch: "Lumina component must be declared in a file whose name (without extension) matches the parent folder name.\n\nCreating components in nested folders is supported as long as the file name matches the immediate parent folder name.",
|
|
272
|
+
extensionNotTsx: "Lumina component must be declared in a .tsx file.",
|
|
273
|
+
noComponentOutsideSrcComponents: "All lumina components must be declared within the src/components folder. Inside that folder, you can create sub-folders for structuring component files."
|
|
274
|
+
},
|
|
275
|
+
type: "problem",
|
|
276
|
+
schema: [],
|
|
277
|
+
fixable: "code"
|
|
278
|
+
},
|
|
279
|
+
defaultOptions: [],
|
|
280
|
+
create(context) {
|
|
281
|
+
return {
|
|
282
|
+
TSModuleDeclaration(node) {
|
|
283
|
+
const luminaDeclarationInterface = extractDeclareElementsInterface(node);
|
|
284
|
+
if (luminaDeclarationInterface === void 0) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const filePath = context.filename;
|
|
288
|
+
const containsSrcComponents = filePath.includes("/src/components/");
|
|
289
|
+
if (!containsSrcComponents) {
|
|
290
|
+
context.report({ messageId: "noComponentOutsideSrcComponents", node: luminaDeclarationInterface });
|
|
291
|
+
}
|
|
292
|
+
const isFileExtensionTsx = filePath.endsWith(".tsx");
|
|
293
|
+
if (!isFileExtensionTsx) {
|
|
294
|
+
context.report({ messageId: "extensionNotTsx", node: luminaDeclarationInterface });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const pathParts = filePath.split("/");
|
|
298
|
+
const folderName = pathParts.at(-2);
|
|
299
|
+
const fileName = pathParts.at(-1).slice(0, -".tsx".length);
|
|
300
|
+
if (folderName !== fileName) {
|
|
301
|
+
context.report({ messageId: "fileFolderNameMismatch", node: luminaDeclarationInterface });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// src/plugins/lumina/rules/consistent-event-naming.ts
|
|
309
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
|
|
310
|
+
var description4 = `Enforce consistent event naming.`;
|
|
311
|
+
var defaultOptions = [
|
|
312
|
+
{
|
|
313
|
+
eventNamespaces: ["arcgis"],
|
|
314
|
+
includeComponentNameInEventName: false
|
|
315
|
+
}
|
|
316
|
+
];
|
|
317
|
+
var consistentEventNaming = createRule({
|
|
318
|
+
name: "consistent-event-naming",
|
|
319
|
+
meta: {
|
|
320
|
+
docs: {
|
|
321
|
+
description: description4
|
|
322
|
+
},
|
|
323
|
+
messages: {
|
|
324
|
+
eventNamespaceError: `Custom event name must start with one of the following prefixes: {{ prefixes }}.
|
|
325
|
+
|
|
326
|
+
Details: https://qawebgis.esri.com/components/lumina/events#best-practices-around-emitting-events`,
|
|
327
|
+
componentNameInEventError: `For consistency, event name should not start with component name.
|
|
328
|
+
|
|
329
|
+
Discussion: https://devtopia.esri.com/WebGIS/arcgis-web-components/discussions/307`,
|
|
330
|
+
noComponentNameInEventError: `For consistency, event name should include component name.`,
|
|
331
|
+
missingPrivateJsDocTag: `Internal and private events must be marked with @internal or @private JSDoc tag.`
|
|
332
|
+
},
|
|
333
|
+
type: "problem",
|
|
334
|
+
schema: [
|
|
335
|
+
{
|
|
336
|
+
type: "object",
|
|
337
|
+
properties: {
|
|
338
|
+
eventNamespaces: {
|
|
339
|
+
type: "array",
|
|
340
|
+
items: {
|
|
341
|
+
type: "string"
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
includeComponentNameInEventName: {
|
|
345
|
+
type: "boolean"
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
additionalProperties: false
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
},
|
|
352
|
+
defaultOptions,
|
|
353
|
+
create(context, options) {
|
|
354
|
+
const eventNamespaces = options[0].eventNamespaces;
|
|
355
|
+
const includeComponentNameInEventName = options[0].includeComponentNameInEventName;
|
|
356
|
+
return {
|
|
357
|
+
PropertyDefinition(node) {
|
|
358
|
+
const isLuminaEvent = isCreateEvent(node);
|
|
359
|
+
if (!isLuminaEvent) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const comments = context.sourceCode.getCommentsBefore(node);
|
|
363
|
+
const isDeprecated = comments.some((comment) => comment.value.includes("@deprecated"));
|
|
364
|
+
if (isDeprecated) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const componentName = context.sourceCode.getAncestors(node).find((ancestor) => ancestor.type === AST_NODE_TYPES4.ClassDeclaration)?.id?.name;
|
|
368
|
+
if (componentName === void 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const propertyName = node.key.type === AST_NODE_TYPES4.Identifier ? node.key.name : node.key.type === AST_NODE_TYPES4.Literal ? node.key.value : void 0;
|
|
372
|
+
if (typeof propertyName !== "string") {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
let eventName = propertyName;
|
|
376
|
+
if (eventNamespaces.length > 0) {
|
|
377
|
+
const currentNamespace = eventNamespaces.find((namespace) => eventName.startsWith(namespace));
|
|
378
|
+
if (currentNamespace === void 0) {
|
|
379
|
+
context.report({
|
|
380
|
+
messageId: "eventNamespaceError",
|
|
381
|
+
data: {
|
|
382
|
+
prefixes: eventNamespaces.join(", ")
|
|
383
|
+
},
|
|
384
|
+
node: node.key
|
|
385
|
+
});
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
eventName = eventName.slice(currentNamespace.length);
|
|
389
|
+
}
|
|
390
|
+
const startsWithInternal = eventName.toLowerCase().startsWith("internal");
|
|
391
|
+
if (startsWithInternal || eventName.toLowerCase().startsWith("private")) {
|
|
392
|
+
const hasInternalJsDocTag = comments.some((comment) => comment.value.includes("@internal"));
|
|
393
|
+
const hasPrivateJsDocTag = comments.some((comment) => comment.value.includes("@private"));
|
|
394
|
+
if (!hasInternalJsDocTag && !hasPrivateJsDocTag) {
|
|
395
|
+
context.report({
|
|
396
|
+
messageId: "missingPrivateJsDocTag",
|
|
397
|
+
node: node.key
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
eventName = startsWithInternal ? eventName.slice("internal".length) : eventName.slice("private".length);
|
|
401
|
+
}
|
|
402
|
+
const capitalIndex = capitalAfterLower.exec(componentName)?.index;
|
|
403
|
+
const unNamespacedComponentName = capitalIndex === void 0 ? void 0 : componentName.slice(capitalIndex) || void 0;
|
|
404
|
+
const includesComponentName = eventName.startsWith(componentName) || unNamespacedComponentName !== void 0 && eventName.startsWith(unNamespacedComponentName);
|
|
405
|
+
if (includeComponentNameInEventName && !includesComponentName) {
|
|
406
|
+
context.report({
|
|
407
|
+
messageId: "noComponentNameInEventError",
|
|
408
|
+
node: node.key
|
|
409
|
+
});
|
|
410
|
+
} else if (!includeComponentNameInEventName && includesComponentName) {
|
|
411
|
+
context.report({
|
|
412
|
+
messageId: "componentNameInEventError",
|
|
413
|
+
node: node.key
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
var capitalAfterLower = /(?<=[a-z\d])[A-Z]/u;
|
|
421
|
+
|
|
422
|
+
// src/plugins/lumina/rules/decorators-context.ts
|
|
423
|
+
var description5 = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
|
|
424
|
+
var decoratorsContext = createRule({
|
|
425
|
+
name: "decorators-context",
|
|
426
|
+
meta: {
|
|
427
|
+
docs: {
|
|
428
|
+
description: description5
|
|
429
|
+
},
|
|
430
|
+
messages: {
|
|
431
|
+
publicApiMustBePublic: `@property(), @method() and createEvent() members must not have private or protected modifier.
|
|
432
|
+
|
|
433
|
+
If you wish to hide this member from public documentation, use @private or @protected JSDoc tags instead. Documentation: https://qawebgis.esri.com/components/lumina/documenting-components#excluding-api-from-public-documentation`,
|
|
434
|
+
noPropertyDecoratorOnMethods: `Methods must not have @property() nor @state() decorator. Did you mean @property() instead?`,
|
|
435
|
+
noCombinedPropertyEvent: `Property may either be an event (initialized with createEvent()) or a property (has @property() decorator), but not both`
|
|
436
|
+
},
|
|
437
|
+
type: "problem",
|
|
438
|
+
schema: [],
|
|
439
|
+
fixable: "code"
|
|
440
|
+
},
|
|
441
|
+
defaultOptions: [],
|
|
442
|
+
create(context) {
|
|
443
|
+
return {
|
|
444
|
+
PropertyDefinition(node) {
|
|
445
|
+
const hasPropertyDecorator = hasDecorator(node, "property");
|
|
446
|
+
const isPrivateOrProtected = node.accessibility === "private" || node.accessibility === "protected";
|
|
447
|
+
if (hasPropertyDecorator && isPrivateOrProtected) {
|
|
448
|
+
context.report({ messageId: "publicApiMustBePublic", node });
|
|
449
|
+
}
|
|
450
|
+
const isLuminaEvent = isCreateEvent(node);
|
|
451
|
+
if (isLuminaEvent && isPrivateOrProtected) {
|
|
452
|
+
context.report({ messageId: "publicApiMustBePublic", node });
|
|
453
|
+
}
|
|
454
|
+
if (isLuminaEvent && hasPropertyDecorator) {
|
|
455
|
+
context.report({ messageId: "noCombinedPropertyEvent", node });
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
MethodDefinition(node) {
|
|
459
|
+
const hasMethodDecorator = hasDecorator(node, "method");
|
|
460
|
+
const hasPropertyDecorator = hasDecorator(node, "property");
|
|
461
|
+
const hasStateDecorator = hasDecorator(node, "state");
|
|
462
|
+
const isPrivateOrProtected = node.accessibility === "private" || node.accessibility === "protected";
|
|
463
|
+
if (hasMethodDecorator && isPrivateOrProtected) {
|
|
464
|
+
context.report({ messageId: "publicApiMustBePublic", node });
|
|
465
|
+
}
|
|
466
|
+
const isRealMethod = node.kind === "method";
|
|
467
|
+
if (isRealMethod && (hasPropertyDecorator || hasStateDecorator)) {
|
|
468
|
+
context.report({ messageId: "noPropertyDecoratorOnMethods", node });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// src/plugins/lumina/rules/no-ignore-jsdoc-tag.ts
|
|
476
|
+
var description6 = `Use @internal or @private JSDoc tag over @ignore. See https://qawebgis.esri.com/components/lumina/documenting-components#excluding-api-from-public-documentation`;
|
|
477
|
+
var noIgnoreJsDocTag = createRule({
|
|
478
|
+
name: "no-ignore-jsdoc-tag",
|
|
479
|
+
meta: {
|
|
480
|
+
docs: {
|
|
481
|
+
description: description6
|
|
482
|
+
},
|
|
483
|
+
messages: {
|
|
484
|
+
noIgnoreJsDocTag: description6
|
|
485
|
+
},
|
|
486
|
+
type: "problem",
|
|
487
|
+
schema: []
|
|
488
|
+
},
|
|
489
|
+
defaultOptions: [],
|
|
490
|
+
create(context) {
|
|
491
|
+
const luminaJsxCheck = checkForLuminaJsx();
|
|
492
|
+
return {
|
|
493
|
+
"ImportDeclaration": luminaJsxCheck,
|
|
494
|
+
"Program:exit"() {
|
|
495
|
+
if (!luminaJsxCheck.isLuminaJsx) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
Array.from(context.sourceCode.text.matchAll(reIgnore), (match) => {
|
|
499
|
+
context.report({
|
|
500
|
+
messageId: "noIgnoreJsDocTag",
|
|
501
|
+
loc: {
|
|
502
|
+
start: context.sourceCode.getLocFromIndex(match.index + "* ".length),
|
|
503
|
+
end: context.sourceCode.getLocFromIndex(match.index + match[0].length)
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
var reIgnore = /\* @ignore/gu;
|
|
512
|
+
|
|
513
|
+
// src/plugins/lumina/rules/no-incorrect-dynamic-tag-name.ts
|
|
514
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES5, ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
|
|
515
|
+
import ts2 from "typescript";
|
|
516
|
+
var description7 = `Detect incorrect usage of dynamic JSX tag name`;
|
|
517
|
+
var noIncorrectDynamicTagName = createRule({
|
|
518
|
+
name: "no-incorrect-dynamic-tag-name",
|
|
519
|
+
meta: {
|
|
520
|
+
docs: {
|
|
521
|
+
description: description7
|
|
522
|
+
},
|
|
523
|
+
messages: {
|
|
524
|
+
incorrectDynamicTagName: `This is using incorrect dynamic tag name syntax. See documentation on how to use dynamic tag in Lumina's JSX: https://qawebgis.esri.com/components/lumina/jsx#dynamic-tag-name`
|
|
525
|
+
},
|
|
526
|
+
type: "problem",
|
|
527
|
+
schema: []
|
|
528
|
+
},
|
|
529
|
+
defaultOptions: [],
|
|
530
|
+
create(context) {
|
|
531
|
+
const services = ESLintUtils3.getParserServices(context);
|
|
532
|
+
const luminaJsxCheck = checkForLuminaJsx();
|
|
533
|
+
return {
|
|
534
|
+
ImportDeclaration: luminaJsxCheck,
|
|
535
|
+
JSXIdentifier(node) {
|
|
536
|
+
if (!luminaJsxCheck.isLuminaJsx) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const isInTagName = node.parent?.type !== AST_NODE_TYPES5.JSXAttribute;
|
|
540
|
+
if (!isInTagName) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const isCapitalized = !node.name.startsWith(node.name.charAt(0).toLowerCase());
|
|
544
|
+
if (!isCapitalized) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const isCorrectDynamicTagName = node.name === "DynamicHtmlTag" || node.name === "DynamicSvgTag";
|
|
548
|
+
if (isCorrectDynamicTagName) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const type = services.getTypeAtLocation(node);
|
|
552
|
+
const isStringType = type.flags & ts2.TypeFlags.StringLike;
|
|
553
|
+
if (isStringType) {
|
|
554
|
+
context.report({
|
|
555
|
+
messageId: "incorrectDynamicTagName",
|
|
556
|
+
node
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// src/plugins/lumina/rules/no-inline-arrow-in-ref.ts
|
|
565
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
|
|
566
|
+
var baseDescription = `Do not pass an inline arrow function to a ref prop - such syntax creates a new function on each render, which makes Lit call ref callback again on each render.`;
|
|
567
|
+
var description8 = `${baseDescription}
|
|
568
|
+
|
|
569
|
+
Alternatives: https://qawebgis.esri.com/components/lumina/jsx#refs`;
|
|
570
|
+
var noInlineArrowInRef = createRule({
|
|
571
|
+
name: "no-inline-arrow-in-ref",
|
|
572
|
+
meta: {
|
|
573
|
+
docs: {
|
|
574
|
+
description: baseDescription
|
|
575
|
+
},
|
|
576
|
+
messages: {
|
|
577
|
+
errorInlineArrow: description8
|
|
578
|
+
},
|
|
579
|
+
type: "problem",
|
|
580
|
+
schema: []
|
|
581
|
+
},
|
|
582
|
+
defaultOptions: [],
|
|
583
|
+
create(context) {
|
|
584
|
+
const luminaJsxCheck = checkForLuminaJsx();
|
|
585
|
+
return {
|
|
586
|
+
ImportDeclaration: luminaJsxCheck,
|
|
587
|
+
JSXAttribute(node) {
|
|
588
|
+
if (!luminaJsxCheck.isLuminaJsx || node.name.name !== "ref" || node.value?.type !== AST_NODE_TYPES6.JSXExpressionContainer) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const initializer = node.value.expression;
|
|
592
|
+
if (initializer.type !== AST_NODE_TYPES6.ArrowFunctionExpression && initializer.type !== AST_NODE_TYPES6.FunctionExpression) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
context.report({
|
|
596
|
+
messageId: "errorInlineArrow",
|
|
597
|
+
node: initializer
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// src/plugins/lumina/rules/no-listen-in-connected-callback.ts
|
|
605
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
|
|
606
|
+
var baseDescription2 = `Do not call this.listen()/this.listenOn() in connectedCallback.`;
|
|
607
|
+
var description9 = `${baseDescription2}
|
|
608
|
+
|
|
609
|
+
Instead, call this.listen()/this.listenOn() in constructor(), load() or loaded().
|
|
610
|
+
|
|
611
|
+
Reason:
|
|
612
|
+
this.listen() automatically creates and cleanups the listener on connect/disconnect.
|
|
613
|
+
Since connectedCallback can be called multiple times, duplicate listeners may be created.`;
|
|
614
|
+
var noListenInConnectedCallback = createRule({
|
|
615
|
+
name: "no-listen-in-connected-callback",
|
|
616
|
+
meta: {
|
|
617
|
+
docs: {
|
|
618
|
+
description: baseDescription2
|
|
619
|
+
},
|
|
620
|
+
messages: {
|
|
621
|
+
errorListenInConnectedCallback: description9
|
|
622
|
+
},
|
|
623
|
+
type: "problem",
|
|
624
|
+
schema: []
|
|
625
|
+
},
|
|
626
|
+
defaultOptions: [],
|
|
627
|
+
create(context) {
|
|
628
|
+
return {
|
|
629
|
+
CallExpression(node) {
|
|
630
|
+
const isListenCall = node.callee.type === AST_NODE_TYPES7.MemberExpression && node.callee.object.type === AST_NODE_TYPES7.ThisExpression && node.callee.property.type === AST_NODE_TYPES7.Identifier && (node.callee.property.name === "listen" || node.callee.property.name === "listenOn");
|
|
631
|
+
if (!isListenCall) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
let hasConnectedCallbackParent = false;
|
|
635
|
+
let currentParent = node.parent;
|
|
636
|
+
while (currentParent) {
|
|
637
|
+
if (currentParent.type === AST_NODE_TYPES7.MethodDefinition) {
|
|
638
|
+
if (currentParent.key.type === AST_NODE_TYPES7.Identifier && currentParent.key.name === "connectedCallback") {
|
|
639
|
+
hasConnectedCallbackParent = true;
|
|
640
|
+
}
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
currentParent = currentParent.parent;
|
|
644
|
+
}
|
|
645
|
+
if (!hasConnectedCallbackParent) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
context.report({
|
|
649
|
+
messageId: "errorListenInConnectedCallback",
|
|
650
|
+
node
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// src/plugins/lumina/rules/no-needless-bind-this.ts
|
|
658
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
|
|
659
|
+
var description10 = `.bind(this) is not necessary in Lit's event listener callbacks and ref callbacks as it is handled automatically.`;
|
|
660
|
+
var noNeedlessBindThis = createRule({
|
|
661
|
+
name: "no-needless-bind-this",
|
|
662
|
+
meta: {
|
|
663
|
+
docs: {
|
|
664
|
+
description: description10
|
|
665
|
+
},
|
|
666
|
+
messages: {
|
|
667
|
+
noNeedlessBindThis: description10
|
|
668
|
+
},
|
|
669
|
+
type: "problem",
|
|
670
|
+
schema: [],
|
|
671
|
+
fixable: "code"
|
|
672
|
+
},
|
|
673
|
+
defaultOptions: [],
|
|
674
|
+
create(context) {
|
|
675
|
+
const luminaJsxCheck = checkForLuminaJsx();
|
|
676
|
+
return {
|
|
677
|
+
ImportDeclaration: luminaJsxCheck,
|
|
678
|
+
JSXExpressionContainer(node) {
|
|
679
|
+
if (!luminaJsxCheck.isLuminaJsx || node.parent.type !== AST_NODE_TYPES8.JSXAttribute || node.parent.name.type !== AST_NODE_TYPES8.JSXIdentifier) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const name = node.parent.name.name;
|
|
683
|
+
const isAutoBindable = name.startsWith("on") || name === "ref";
|
|
684
|
+
if (!isAutoBindable) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const expression = node.expression.type === AST_NODE_TYPES8.ChainExpression ? node.expression.expression : node.expression;
|
|
688
|
+
const isCallWithThis = (
|
|
689
|
+
// expression(...)
|
|
690
|
+
expression.type === AST_NODE_TYPES8.CallExpression && // expression(expression)
|
|
691
|
+
expression.arguments.length === 1 && // expression(this)
|
|
692
|
+
expression.arguments[0].type === AST_NODE_TYPES8.ThisExpression
|
|
693
|
+
);
|
|
694
|
+
if (!isCallWithThis) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const callee = expression.callee;
|
|
698
|
+
const isBindThisCall = (
|
|
699
|
+
// expression.expression(this)
|
|
700
|
+
callee.type === AST_NODE_TYPES8.MemberExpression && // expression.identifier(this)
|
|
701
|
+
callee.property.type === AST_NODE_TYPES8.Identifier && // expression.bind(this)
|
|
702
|
+
callee.property.name === "bind" && // expression.expression.bind(this)
|
|
703
|
+
callee.object.type === AST_NODE_TYPES8.MemberExpression && // expression.identifier.bind(this)
|
|
704
|
+
callee.object.property.type === AST_NODE_TYPES8.Identifier && // this.identifier.bind(this)
|
|
705
|
+
callee.object.object.type === AST_NODE_TYPES8.ThisExpression
|
|
706
|
+
);
|
|
707
|
+
if (!isBindThisCall) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const tagName = context.sourceCode.getAncestors(node.parent).find((ancestor) => ancestor.type === AST_NODE_TYPES8.JSXOpeningElement)?.name;
|
|
711
|
+
const tagNameString = tagName?.type === AST_NODE_TYPES8.JSXIdentifier ? tagName.name : void 0;
|
|
712
|
+
if (tagNameString === void 0) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const isHtmlElement = tagNameString.startsWith(tagNameString.charAt(0).toLowerCase()) || tagNameString === "DynamicHtmlTag" || tagNameString === "DynamicSvgTag";
|
|
716
|
+
if (!isHtmlElement) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
context.report({
|
|
720
|
+
messageId: "noNeedlessBindThis",
|
|
721
|
+
node: callee.property,
|
|
722
|
+
fix(fixer) {
|
|
723
|
+
return fixer.replaceText(expression, context.sourceCode.getText(callee.object));
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// src/plugins/lumina/rules/no-property-name-start-with-on.ts
|
|
732
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
|
|
733
|
+
var description11 = `Do not start public property names with "on" as that can confuse frameworks into thinking this property is an event`;
|
|
734
|
+
var noPropertyNameStartWithOn = createRule({
|
|
735
|
+
name: "no-property-name-start-with-on",
|
|
736
|
+
meta: {
|
|
737
|
+
docs: {
|
|
738
|
+
description: description11
|
|
739
|
+
},
|
|
740
|
+
messages: {
|
|
741
|
+
noPropertyNameStartWithOn: description11
|
|
742
|
+
},
|
|
743
|
+
type: "problem",
|
|
744
|
+
schema: []
|
|
745
|
+
},
|
|
746
|
+
defaultOptions: [],
|
|
747
|
+
create(context) {
|
|
748
|
+
return {
|
|
749
|
+
PropertyDefinition(node) {
|
|
750
|
+
const isPublicProperty = hasDecorator(node, "property");
|
|
751
|
+
if (!isPublicProperty) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const propertyName = node.key.type === AST_NODE_TYPES9.Identifier ? node.key.name : node.key.type === AST_NODE_TYPES9.Literal ? node.key.value : void 0;
|
|
755
|
+
if (typeof propertyName !== "string") {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (!propertyName.startsWith("on")) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const comments = context.sourceCode.getCommentsBefore(node);
|
|
762
|
+
const isDeprecated = comments.some((comment) => comment.value.includes("@deprecated"));
|
|
763
|
+
if (isDeprecated) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
context.report({
|
|
767
|
+
messageId: "noPropertyNameStartWithOn",
|
|
768
|
+
node: node.key
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// src/plugins/lumina/rules/no-render-false.ts
|
|
776
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES10, ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
|
|
777
|
+
|
|
778
|
+
// src/plugins/lumina/utils/checker.ts
|
|
779
|
+
import ts3 from "typescript";
|
|
780
|
+
var litTemplateResult = "TemplateResult";
|
|
781
|
+
var explicitJsxTypeName = "JsxNode";
|
|
782
|
+
var implicitJsxTypeName = "Element";
|
|
783
|
+
var implicitJsxParentName = "LuminaJsx";
|
|
784
|
+
function isLuminaJsxType(type) {
|
|
785
|
+
for (const declaration of type.aliasSymbol?.declarations ?? []) {
|
|
786
|
+
if (!ts3.isTypeAliasDeclaration(declaration)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const typeName = declaration.name.escapedText;
|
|
790
|
+
if (typeName === litTemplateResult || typeName === explicitJsxTypeName) {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
if (typeName !== implicitJsxTypeName) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
const grandParent = declaration.parent?.parent;
|
|
797
|
+
if (!ts3.isModuleDeclaration(grandParent)) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
const symbol = grandParent.symbol;
|
|
801
|
+
if (symbol?.escapedName === implicitJsxParentName) {
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/plugins/lumina/rules/no-render-false.ts
|
|
809
|
+
var baseDescription3 = `Avoid accidentally rendering "false" to the screen (Lit stringifies booleans, rather than drop them like React/Stencil).`;
|
|
810
|
+
var description12 = `${baseDescription3}
|
|
811
|
+
|
|
812
|
+
Lumina automatically handles some cases where "false" may get rendered to the screen, but the pattern this code is using is not handled.`;
|
|
813
|
+
var noRenderFalse = createRule({
|
|
814
|
+
name: "no-render-false",
|
|
815
|
+
meta: {
|
|
816
|
+
docs: {
|
|
817
|
+
description: baseDescription3
|
|
818
|
+
},
|
|
819
|
+
messages: {
|
|
820
|
+
errorFalseRendered: description12
|
|
821
|
+
},
|
|
822
|
+
type: "problem",
|
|
823
|
+
schema: [],
|
|
824
|
+
fixable: "code"
|
|
825
|
+
},
|
|
826
|
+
defaultOptions: [],
|
|
827
|
+
create(context) {
|
|
828
|
+
const services = ESLintUtils4.getParserServices(context);
|
|
829
|
+
return {
|
|
830
|
+
LogicalExpression(node) {
|
|
831
|
+
if (node.operator !== "&&") {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const type = services.getTypeAtLocation(node.right);
|
|
835
|
+
if (!isLuminaJsxType(type)) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (node.parent.type === AST_NODE_TYPES10.JSXExpressionContainer) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const right = unwrapExpression(node.right);
|
|
842
|
+
if (right.type === AST_NODE_TYPES10.JSXElement) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (node.parent.type === AST_NODE_TYPES10.LogicalExpression) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
context.report({
|
|
849
|
+
messageId: "errorFalseRendered",
|
|
850
|
+
node,
|
|
851
|
+
fix(fixer) {
|
|
852
|
+
const leftText = context.sourceCode.getText(node.left);
|
|
853
|
+
const rightText = context.sourceCode.getText(node.right);
|
|
854
|
+
const replacement = `${leftText} ? ${rightText} : ""`;
|
|
855
|
+
return fixer.replaceText(node, replacement);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// src/plugins/lumina/rules/tag-name-rules.ts
|
|
864
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
|
|
865
|
+
var description13 = `Require tag names to start with a given namespace`;
|
|
866
|
+
var defaultOptions2 = [
|
|
867
|
+
{
|
|
868
|
+
namespaces: ["arcgis-"]
|
|
869
|
+
}
|
|
870
|
+
];
|
|
871
|
+
var tagNameRules = createRule({
|
|
872
|
+
name: "tag-name-rules",
|
|
873
|
+
meta: {
|
|
874
|
+
docs: {
|
|
875
|
+
description: description13
|
|
876
|
+
},
|
|
877
|
+
messages: {
|
|
878
|
+
requireNamespace: "Custom element tag names in this project must start with one of the following namespaces: {{namespaces}}",
|
|
879
|
+
noComputedTagName: "Computed tag names are not allowed",
|
|
880
|
+
noNonStringTagName: 'Tag names must be strings like "arcgis-click" rather than numbers',
|
|
881
|
+
noTagNameIdentifier: "Tag name must include a dash",
|
|
882
|
+
unexpectedDeclareElementsEntry: "Unexpected entry in declare elements interface. Expected a property signature"
|
|
883
|
+
},
|
|
884
|
+
type: "problem",
|
|
885
|
+
schema: [
|
|
886
|
+
{
|
|
887
|
+
type: "object",
|
|
888
|
+
properties: {
|
|
889
|
+
namespaces: {
|
|
890
|
+
type: "array",
|
|
891
|
+
items: {
|
|
892
|
+
type: "string"
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
additionalProperties: false
|
|
897
|
+
}
|
|
898
|
+
],
|
|
899
|
+
fixable: "code"
|
|
900
|
+
},
|
|
901
|
+
defaultOptions: defaultOptions2,
|
|
902
|
+
create(context, options) {
|
|
903
|
+
return {
|
|
904
|
+
TSModuleDeclaration(node) {
|
|
905
|
+
const luminaDeclarationInterface = extractDeclareElementsInterface(node);
|
|
906
|
+
if (luminaDeclarationInterface === void 0) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
luminaDeclarationInterface.body.body.forEach((member) => {
|
|
910
|
+
if (member.type !== AST_NODE_TYPES11.TSPropertySignature) {
|
|
911
|
+
context.report({
|
|
912
|
+
messageId: "unexpectedDeclareElementsEntry",
|
|
913
|
+
node: member
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (member.computed) {
|
|
918
|
+
context.report({
|
|
919
|
+
messageId: "noComputedTagName",
|
|
920
|
+
node: member.key
|
|
921
|
+
});
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (member.key.type === AST_NODE_TYPES11.Identifier) {
|
|
925
|
+
context.report({
|
|
926
|
+
messageId: "noTagNameIdentifier",
|
|
927
|
+
node: member.key
|
|
928
|
+
});
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (typeof member.key.value !== "string") {
|
|
932
|
+
context.report({
|
|
933
|
+
messageId: "noNonStringTagName",
|
|
934
|
+
node: member.key
|
|
935
|
+
});
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const tagName = member.key.value;
|
|
939
|
+
const namespaces = options[0].namespaces;
|
|
940
|
+
if (namespaces.length > 0 && !namespaces.some((ns) => tagName.startsWith(ns))) {
|
|
941
|
+
context.report({
|
|
942
|
+
messageId: "requireNamespace",
|
|
943
|
+
node: member.key,
|
|
944
|
+
data: {
|
|
945
|
+
namespaces: namespaces.join(", ")
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// src/plugins/lumina/index.ts
|
|
956
|
+
var lumina_default = makeEslintPlugin("lumina", {
|
|
957
|
+
"add-missing-jsx-import": addMissingJsxImport,
|
|
958
|
+
"auto-add-type": autoAddType,
|
|
959
|
+
"component-placement-rules": componentPlacementRules,
|
|
960
|
+
"consistent-event-naming": consistentEventNaming,
|
|
961
|
+
"decorators-context": decoratorsContext,
|
|
962
|
+
"no-ignore-jsdoc-tag": noIgnoreJsDocTag,
|
|
963
|
+
"no-incorrect-dynamic-tag-name": noIncorrectDynamicTagName,
|
|
964
|
+
"no-inline-arrow-in-ref": noInlineArrowInRef,
|
|
965
|
+
"no-listen-in-connected-callback": noListenInConnectedCallback,
|
|
966
|
+
"no-needless-bind-this": noNeedlessBindThis,
|
|
967
|
+
"no-property-name-start-with-on": noPropertyNameStartWithOn,
|
|
968
|
+
"no-render-false": noRenderFalse,
|
|
969
|
+
"tag-name-rules": tagNameRules
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
export {
|
|
973
|
+
lumina_default
|
|
974
|
+
};
|