@arcgis/eslint-config 4.33.0-next.60 → 4.33.0-next.62

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.
Files changed (66) hide show
  1. package/dist/config/extra.js +2 -3
  2. package/dist/config/index.d.ts +1 -18
  3. package/dist/config/index.js +4 -9
  4. package/dist/config/lumina.d.ts +1 -5
  5. package/dist/config/lumina.js +6 -13
  6. package/dist/config/storybook.d.ts +1 -1
  7. package/dist/{chunk-3NHRERBA.js → makePlugin-dhgL5LDQ.js} +2 -6
  8. package/dist/plugins/lumina/index.js +1855 -5
  9. package/dist/plugins/lumina/rules/add-missing-jsx-import.d.ts +1 -1
  10. package/dist/plugins/lumina/rules/auto-add-type.d.ts +2 -2
  11. package/dist/plugins/lumina/rules/component-placement-rules.d.ts +1 -1
  12. package/dist/plugins/lumina/rules/consistent-event-naming.d.ts +1 -1
  13. package/dist/plugins/lumina/rules/decorators-context.d.ts +1 -1
  14. package/dist/plugins/lumina/rules/member-ordering/build.d.ts +3 -3
  15. package/dist/plugins/lumina/rules/member-ordering/comments.d.ts +2 -2
  16. package/dist/plugins/lumina/rules/member-ordering/config.d.ts +1 -1
  17. package/dist/plugins/lumina/rules/member-ordering/normalize.d.ts +2 -2
  18. package/dist/plugins/lumina/rules/member-ordering.d.ts +1 -1
  19. package/dist/plugins/lumina/rules/no-ignore-jsdoc-tag.d.ts +1 -1
  20. package/dist/plugins/lumina/rules/no-incorrect-dynamic-tag-name.d.ts +2 -2
  21. package/dist/plugins/lumina/rules/no-inline-arrow-in-ref.d.ts +1 -1
  22. package/dist/plugins/lumina/rules/no-invalid-directives-prop.d.ts +1 -1
  23. package/dist/plugins/lumina/rules/no-jsx-spread.d.ts +1 -1
  24. package/dist/plugins/lumina/rules/no-listen-in-connected-callback.d.ts +1 -1
  25. package/dist/plugins/lumina/rules/no-non-component-exports.d.ts +1 -1
  26. package/dist/plugins/lumina/rules/no-property-name-start-with-on.d.ts +1 -1
  27. package/dist/plugins/lumina/rules/no-render-false.d.ts +2 -2
  28. package/dist/plugins/lumina/rules/no-unnecessary-attribute-name.d.ts +1 -1
  29. package/dist/plugins/lumina/rules/no-unnecessary-bind-this.d.ts +1 -1
  30. package/dist/plugins/lumina/rules/no-unnecessary-key.d.ts +1 -1
  31. package/dist/plugins/lumina/rules/tag-name-rules.d.ts +1 -1
  32. package/dist/plugins/lumina/utils/checker.d.ts +1 -1
  33. package/dist/plugins/lumina/utils/creator.d.ts +2 -2
  34. package/dist/plugins/lumina/utils/estree.d.ts +1 -1
  35. package/dist/plugins/utils/makePlugin.d.ts +2 -9
  36. package/dist/plugins/utils/tests.d.ts +1 -1
  37. package/dist/plugins/webgis/index.js +142 -5
  38. package/dist/plugins/webgis/rules/no-import-outside-src.d.ts +1 -1
  39. package/dist/plugins/webgis/rules/no-touching-jsdoc.d.ts +1 -1
  40. package/dist/plugins/webgis/rules/require-js-in-core-import.d.ts +1 -1
  41. package/dist/plugins/webgis/utils/creator.d.ts +2 -2
  42. package/package.json +2 -2
  43. package/dist/chunk-3N4LXZ2U.js +0 -1978
  44. package/dist/chunk-W2DG52NG.js +0 -160
  45. package/dist/plugins/lumina/rules/add-missing-jsx-import.test.d.ts +0 -1
  46. package/dist/plugins/lumina/rules/auto-add-type.test.d.ts +0 -1
  47. package/dist/plugins/lumina/rules/component-placement-rules.test.d.ts +0 -1
  48. package/dist/plugins/lumina/rules/consistent-event-naming.test.d.ts +0 -1
  49. package/dist/plugins/lumina/rules/decorators-context.test.d.ts +0 -1
  50. package/dist/plugins/lumina/rules/member-ordering.test.d.ts +0 -1
  51. package/dist/plugins/lumina/rules/no-ignore-jsdoc-tag.test.d.ts +0 -1
  52. package/dist/plugins/lumina/rules/no-incorrect-dynamic-tag-name.test.d.ts +0 -1
  53. package/dist/plugins/lumina/rules/no-inline-arrow-in-ref.test.d.ts +0 -1
  54. package/dist/plugins/lumina/rules/no-invalid-directives-prop.test.d.ts +0 -1
  55. package/dist/plugins/lumina/rules/no-jsx-spread.test.d.ts +0 -1
  56. package/dist/plugins/lumina/rules/no-listen-in-connected-callback.test.d.ts +0 -1
  57. package/dist/plugins/lumina/rules/no-non-component-exports.test.d.ts +0 -1
  58. package/dist/plugins/lumina/rules/no-property-name-start-with-on.test.d.ts +0 -1
  59. package/dist/plugins/lumina/rules/no-render-false.test.d.ts +0 -1
  60. package/dist/plugins/lumina/rules/no-unnecessary-attribute-name.test.d.ts +0 -1
  61. package/dist/plugins/lumina/rules/no-unnecessary-bind-this.test.d.ts +0 -1
  62. package/dist/plugins/lumina/rules/no-unnecessary-key.test.d.ts +0 -1
  63. package/dist/plugins/lumina/rules/tag-name-rules.spec.d.ts +0 -1
  64. package/dist/plugins/webgis/rules/no-import-outside-src.test.d.ts +0 -1
  65. package/dist/plugins/webgis/rules/no-touching-jsdoc.test.d.ts +0 -1
  66. package/dist/plugins/webgis/rules/require-js-in-core-import.test.d.ts +0 -1
@@ -1,1978 +0,0 @@
1
- import {
2
- makeEslintPlugin
3
- } from "./chunk-3NHRERBA.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 luminaTestEntrypointName = "@arcgis/lumina-compiler/testing";
19
- var luminaJsxExportName = "h";
20
- function checkForLuminaJsx() {
21
- const ImportDeclaration = (node) => {
22
- if (node.source.value !== luminaEntrypointName) {
23
- return;
24
- }
25
- for (const specifier of node.specifiers) {
26
- 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)) {
27
- withProperty.isLuminaJsx = true;
28
- return;
29
- }
30
- }
31
- };
32
- const withProperty = ImportDeclaration;
33
- return withProperty;
34
- }
35
- function hasDecorator(node, decoratorName) {
36
- return node.decorators.some(
37
- (decorator) => decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === decoratorName
38
- ) ?? false;
39
- }
40
- function extractDeclareElementsInterface(node) {
41
- return node.kind === "global" ? node.body.body.find(
42
- (node2) => node2.type === AST_NODE_TYPES.TSInterfaceDeclaration && node2.id.name === "DeclareElements"
43
- ) : void 0;
44
- }
45
- function isCreateEvent(node) {
46
- return node.value?.type === AST_NODE_TYPES.CallExpression && node.value.callee.type === AST_NODE_TYPES.Identifier && node.value.callee.name === "createEvent" && !node.static;
47
- }
48
- var getProperty = (properties, name) => properties?.find(
49
- (option) => option.type === AST_NODE_TYPES.Property && option.key.type === AST_NODE_TYPES.Identifier && option.key.name === name
50
- )?.value;
51
- function isGetterWithoutSetter(node) {
52
- const isGetter = node.type === AST_NODE_TYPES.MethodDefinition && node.kind === "get";
53
- if (!isGetter) {
54
- return false;
55
- }
56
- const index = node.parent.body.indexOf(node);
57
- const previousNode = node.parent.body.at(index - 1);
58
- const nextNode = node.parent.body.at(index + 1);
59
- const name = getName(node);
60
- if (name === void 0) {
61
- return false;
62
- }
63
- const previousIsSetter = previousNode?.type === AST_NODE_TYPES.MethodDefinition && previousNode.kind === "set" && getName(previousNode) === name;
64
- if (previousIsSetter) {
65
- return false;
66
- }
67
- const nextIsSetter = nextNode?.type === AST_NODE_TYPES.MethodDefinition && nextNode.kind === "set" && getName(nextNode) === name;
68
- return !nextIsSetter;
69
- }
70
- function getName(node) {
71
- if (node.key.type === AST_NODE_TYPES.Identifier) {
72
- return node.key.name;
73
- } else if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === "string") {
74
- return node.key.value;
75
- } else {
76
- return void 0;
77
- }
78
- }
79
- function parsePropertyDecorator(decorator) {
80
- const isPropertyDecorator = decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === "property";
81
- if (!isPropertyDecorator) {
82
- return;
83
- }
84
- const callExpression = decorator?.expression.type === AST_NODE_TYPES.CallExpression ? decorator.expression : void 0;
85
- if (callExpression === void 0) {
86
- return;
87
- }
88
- const options = callExpression.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression ? callExpression.arguments[0] : void 0;
89
- const properties = options?.properties;
90
- return {
91
- callExpression,
92
- options,
93
- properties
94
- };
95
- }
96
- function isBindThisCallee(callee) {
97
- return callee.type === AST_NODE_TYPES.MemberExpression && // expression.identifier(this)
98
- callee.property.type === AST_NODE_TYPES.Identifier && // expression.bind(this)
99
- callee.property.name === "bind" && // expression.expression.bind(this)
100
- callee.object.type === AST_NODE_TYPES.MemberExpression && // expression.identifier.bind(this)
101
- callee.object.property.type === AST_NODE_TYPES.Identifier && // this.identifier.bind(this)
102
- callee.object.object.type === AST_NODE_TYPES.ThisExpression;
103
- }
104
-
105
- // src/plugins/lumina/rules/add-missing-jsx-import.ts
106
- var importDeclaration = `import { ${luminaJsxExportName} } from "${luminaEntrypointName}";`;
107
- var description = `To use Lumina's JSX, you need to ${importDeclaration}`;
108
- var addMissingJsxImport = createRule({
109
- name: "add-missing-jsx-import",
110
- meta: {
111
- docs: {
112
- description,
113
- defaultLevel: "error"
114
- },
115
- messages: {
116
- addMissingJsxImport: description
117
- },
118
- type: "problem",
119
- schema: [],
120
- fixable: "code"
121
- },
122
- defaultOptions: [],
123
- create(context) {
124
- let errorAlreadyReported = false;
125
- let isUsingLumina = false;
126
- let lastImportDeclaration;
127
- let lastLuminaImportClause;
128
- let hasLuminaJsxImport = false;
129
- return {
130
- ImportDeclaration(node) {
131
- lastImportDeclaration = node;
132
- if (node.source.value === luminaTestEntrypointName) {
133
- isUsingLumina = true;
134
- return;
135
- }
136
- if (node.source.value !== luminaEntrypointName) {
137
- return;
138
- }
139
- isUsingLumina = true;
140
- const isTypeOnly = node.importKind === "type";
141
- for (const specifier of node.specifiers) {
142
- if (!isTypeOnly) {
143
- lastLuminaImportClause = specifier;
144
- }
145
- 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)) {
146
- hasLuminaJsxImport = true;
147
- return;
148
- }
149
- }
150
- },
151
- JSXIdentifier(node) {
152
- if (isUsingLumina && !hasLuminaJsxImport && !errorAlreadyReported) {
153
- errorAlreadyReported = true;
154
- context.report({
155
- messageId: "addMissingJsxImport",
156
- node,
157
- fix(fixer) {
158
- if (lastLuminaImportClause !== void 0) {
159
- return fixer.insertTextAfter(lastLuminaImportClause, `, ${luminaJsxExportName}`);
160
- }
161
- if (lastImportDeclaration !== void 0) {
162
- return fixer.insertTextAfter(lastImportDeclaration, importDeclaration);
163
- } else {
164
- return fixer.insertTextBefore(context.sourceCode.ast, importDeclaration);
165
- }
166
- }
167
- });
168
- }
169
- }
170
- };
171
- }
172
- });
173
-
174
- // src/plugins/lumina/rules/auto-add-type.ts
175
- import { AST_NODE_TYPES as AST_NODE_TYPES3, ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
176
- import ts from "typescript";
177
- var description2 = "Auto add { type: Boolean } or { type: Number } where necessary";
178
- var autoAddType = createRule({
179
- name: "auto-add-type",
180
- meta: {
181
- docs: {
182
- description: description2,
183
- defaultLevel: "warn"
184
- },
185
- messages: {
186
- 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.
187
-
188
- More information: https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/1991`,
189
- typeAnnotationMismatchesActualType: "The @property({type: {{type}} }) doesn't match the actual property type ({{actualType}}).",
190
- noEmptyPropertyObject: "Replace @property({}) with @property()",
191
- noUnnecessaryType: "Property type is trivially inferrable without type-checking - remove needless { type: {{ type }} } annotation.",
192
- unhandledType: `The default Lit attribute<-->property converter does not define any behavior for @property({ type: {{type}} }). Consider removing this type annotation, or adding a custom converter @property({ converter: ... }) to handle this type`
193
- },
194
- type: "problem",
195
- schema: [],
196
- fixable: "code"
197
- },
198
- defaultOptions: [],
199
- create(context) {
200
- const services = ESLintUtils2.getParserServices(context);
201
- return {
202
- Decorator(decorator) {
203
- const part = parsePropertyDecorator(decorator);
204
- if (part === void 0) {
205
- return;
206
- }
207
- const { options, properties, callExpression } = part;
208
- const property = decorator.parent;
209
- if (property?.type !== AST_NODE_TYPES3.MethodDefinition && property?.type !== AST_NODE_TYPES3.PropertyDefinition) {
210
- return;
211
- }
212
- const trivialType = inferTrivialType(property);
213
- const typeProperty = getProperty(properties, "type");
214
- const converterProperty = getProperty(properties, "converter");
215
- const isTrivialType = trivialType === "Number" || trivialType === "Boolean";
216
- if (isTrivialType && typeProperty !== void 0 && typeProperty.type === AST_NODE_TYPES3.Identifier && (typeProperty.name === "Number" || typeProperty.name === "Boolean")) {
217
- context.report({
218
- node: typeProperty,
219
- messageId: "noUnnecessaryType",
220
- data: {
221
- type: typeProperty.name
222
- },
223
- fix(fixer) {
224
- const nextToken = context.sourceCode.getTokenAfter(typeProperty.parent);
225
- const isTrailingComma = nextToken && nextToken.value === ",";
226
- return isTrailingComma ? fixer.removeRange([typeProperty.parent.range[0], nextToken.range[1]]) : fixer.remove(typeProperty.parent);
227
- }
228
- });
229
- return;
230
- }
231
- if (
232
- // Do not emit any warnings if has "converter" as we don't know what the converter does
233
- converterProperty === void 0 && typeProperty?.type === AST_NODE_TYPES3.Identifier && !builtInConverterTypes.has(typeProperty.name)
234
- ) {
235
- context.report({
236
- node: typeProperty,
237
- messageId: "unhandledType",
238
- data: {
239
- type: typeProperty.name
240
- }
241
- });
242
- return;
243
- }
244
- if (isTrivialType) {
245
- return;
246
- }
247
- if (options !== void 0 && properties?.length === 0) {
248
- context.report({
249
- node: options,
250
- messageId: "noEmptyPropertyObject",
251
- fix(fixer) {
252
- return fixer.replaceText(
253
- options,
254
- context.sourceCode.getCommentsInside(options).map((comment) => context.sourceCode.getText(comment)).join("\n")
255
- );
256
- }
257
- });
258
- return;
259
- }
260
- const attributeOption = getProperty(properties, "attribute");
261
- const isAttributeFalse = attributeOption?.type === AST_NODE_TYPES3.Literal && attributeOption.value === false;
262
- if (isAttributeFalse) {
263
- return;
264
- }
265
- const type = services.getTypeAtLocation(property);
266
- const typeFlags = (type.flags & ts.TypeFlags.Union ? type.types : [type]).reduce(
267
- (flags, type2) => flags | type2.flags,
268
- 0
269
- );
270
- const isTypeCastable = (typeFlags & (ts.TypeFlags.String | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0;
271
- const isNumberType = isTypeCastable && typeFlags & ts.TypeFlags.NumberLike;
272
- const isBooleanType = isTypeCastable && typeFlags & ts.TypeFlags.BooleanLike;
273
- if (isNumberType && isBooleanType) {
274
- return;
275
- }
276
- if (!isNumberType && !isBooleanType) {
277
- return;
278
- }
279
- if (typeProperty !== void 0) {
280
- if (
281
- // Do not emit any warnings if has "converter" as we don't know what the converter does
282
- converterProperty === void 0 && typeProperty.type === AST_NODE_TYPES3.Identifier && (typeProperty.name === "Number" && isBooleanType || typeProperty.name === "Boolean" && isNumberType)
283
- ) {
284
- context.report({
285
- node: typeProperty,
286
- messageId: "typeAnnotationMismatchesActualType",
287
- data: {
288
- type: typeProperty.name,
289
- actualType: isNumberType ? "Number" : "Boolean"
290
- }
291
- });
292
- }
293
- return;
294
- }
295
- const comments = context.sourceCode.getCommentsBefore(property);
296
- const isDocsOnlyReadOnly = comments.some((comment) => comment.value.includes("@readonly"));
297
- const readOnlyProperty = getProperty(properties, "readOnly");
298
- const hasReadOnlyFlag = readOnlyProperty?.type === AST_NODE_TYPES3.Literal && readOnlyProperty.value === true;
299
- const isReadOnly = isDocsOnlyReadOnly || hasReadOnlyFlag || isGetterWithoutSetter(property);
300
- const reflectsProperty = getProperty(properties, "reflects");
301
- if (isReadOnly && (!isBooleanType || reflectsProperty?.type !== AST_NODE_TYPES3.Literal || reflectsProperty.value !== true)) {
302
- return;
303
- }
304
- context.report({
305
- messageId: "addType",
306
- data: {
307
- type: isNumberType ? "Number" : "Boolean"
308
- },
309
- node: property,
310
- fix(fixer) {
311
- const sourceCode = context.sourceCode;
312
- if (options === void 0) {
313
- const token = sourceCode.getTokenAfter(callExpression.callee, {
314
- filter: (token2) => token2.value === "("
315
- });
316
- return fixer.insertTextAfterRange(token.range, `{ type: ${isNumberType ? "Number" : "Boolean"} }`);
317
- } else {
318
- return fixer.insertTextBefore(options.properties[0], `type: ${isNumberType ? "Number" : "Boolean"}, `);
319
- }
320
- }
321
- });
322
- }
323
- };
324
- }
325
- });
326
- function inferTrivialType(member) {
327
- 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;
328
- if (type === void 0) {
329
- 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 === "-") {
330
- return "Number";
331
- }
332
- if (member.value?.type === AST_NODE_TYPES3.Literal && typeof member.value.value === "boolean") {
333
- return "Boolean";
334
- }
335
- } else if (type.type === AST_NODE_TYPES3.TSNumberKeyword) {
336
- return "Number";
337
- } else if (type.type === AST_NODE_TYPES3.TSBooleanKeyword) {
338
- return "Boolean";
339
- }
340
- return "Other";
341
- }
342
- var builtInConverterTypes = /* @__PURE__ */ new Set(["Number", "Boolean", "Array", "Object"]);
343
-
344
- // ../components-build-utils/dist/index.js
345
- import { sep as sep2 } from "path";
346
- import posix from "path/posix";
347
- import win32 from "path/win32";
348
- var isPosix = sep2 === posix.sep;
349
- var toPosixPathSeparators = (relativePath) => relativePath.includes(win32.sep) ? relativePath.replaceAll(win32.sep, posix.sep) : relativePath;
350
- var getCwd = isPosix ? process.cwd : () => toPosixPathSeparators(process.cwd());
351
- var path = isPosix ? posix : {
352
- ...win32,
353
- sep: posix.sep,
354
- join(...paths) {
355
- const result = win32.join(...paths);
356
- return toPosixPathSeparators(result);
357
- },
358
- normalize(path2) {
359
- const result = win32.normalize(path2);
360
- return toPosixPathSeparators(result);
361
- },
362
- relative(from, to) {
363
- const result = win32.relative(from, to);
364
- return toPosixPathSeparators(result);
365
- },
366
- dirname(path2) {
367
- const result = win32.dirname(path2);
368
- return toPosixPathSeparators(result);
369
- },
370
- resolve(...paths) {
371
- const result = win32.resolve(...paths);
372
- return toPosixPathSeparators(result);
373
- },
374
- toNamespacedPath(path2) {
375
- const result = win32.toNamespacedPath(path2);
376
- return toPosixPathSeparators(result);
377
- }
378
- };
379
-
380
- // src/plugins/lumina/rules/component-placement-rules.ts
381
- var description3 = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
382
- var componentPlacementRules = createRule({
383
- name: "component-placement-rules",
384
- meta: {
385
- docs: {
386
- description: description3,
387
- defaultLevel: "error"
388
- },
389
- messages: {
390
- 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.",
391
- extensionNotTsx: "Lumina component must be declared in a .tsx file.",
392
- noComponentOutsideSrcComponents: "All lumina components must be declared within the src/components folder. Inside that folder, you can create sub-folders for structuring component files."
393
- },
394
- type: "problem",
395
- schema: [],
396
- fixable: "code"
397
- },
398
- defaultOptions: [],
399
- create(context) {
400
- return {
401
- TSModuleDeclaration(node) {
402
- const luminaDeclarationInterface = extractDeclareElementsInterface(node);
403
- if (luminaDeclarationInterface === void 0) {
404
- return;
405
- }
406
- const filePath = toPosixPathSeparators(context.filename);
407
- const containsSrcComponents = filePath.includes("/src/components/");
408
- if (!containsSrcComponents) {
409
- context.report({ messageId: "noComponentOutsideSrcComponents", node: luminaDeclarationInterface });
410
- }
411
- const isFileExtensionTsx = filePath.endsWith(".tsx");
412
- if (!isFileExtensionTsx) {
413
- context.report({ messageId: "extensionNotTsx", node: luminaDeclarationInterface });
414
- return;
415
- }
416
- const [fileName, folderName] = filePath.split("/").reverse();
417
- const extensionlessFileName = fileName.slice(0, -".tsx".length);
418
- if (folderName !== extensionlessFileName) {
419
- context.report({ messageId: "fileFolderNameMismatch", node: luminaDeclarationInterface });
420
- }
421
- }
422
- };
423
- }
424
- });
425
-
426
- // src/plugins/lumina/rules/consistent-event-naming.ts
427
- import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
428
- var description4 = `Enforce consistent event naming.`;
429
- var defaultOptions = [
430
- {
431
- eventNamespaces: ["arcgis"],
432
- includeComponentNameInEventName: false
433
- }
434
- ];
435
- var consistentEventNaming = createRule({
436
- name: "consistent-event-naming",
437
- meta: {
438
- docs: {
439
- description: description4,
440
- defaultLevel: "warn"
441
- },
442
- messages: {
443
- eventNamespaceError: `Custom event name must start with one of the following prefixes: {{ prefixes }}.
444
-
445
- Details: https://qawebgis.esri.com/components/lumina/events#best-practices-around-emitting-events`,
446
- componentNameInEventError: `For consistency, event name should not start with component name.
447
-
448
- Discussion: https://devtopia.esri.com/WebGIS/arcgis-web-components/discussions/307`,
449
- noComponentNameInEventError: `For consistency, event name should include component name.`,
450
- missingPrivateJsDocTag: `Internal and private events must be marked with @internal or @private JSDoc tag.`
451
- },
452
- type: "problem",
453
- schema: [
454
- {
455
- type: "object",
456
- properties: {
457
- eventNamespaces: {
458
- type: "array",
459
- items: {
460
- type: "string"
461
- }
462
- },
463
- includeComponentNameInEventName: {
464
- type: "boolean"
465
- }
466
- },
467
- additionalProperties: false
468
- }
469
- ]
470
- },
471
- defaultOptions,
472
- create(context, options) {
473
- const eventNamespaces = options[0].eventNamespaces;
474
- const includeComponentNameInEventName = options[0].includeComponentNameInEventName;
475
- return {
476
- PropertyDefinition(node) {
477
- const isLuminaEvent = isCreateEvent(node);
478
- if (!isLuminaEvent) {
479
- return;
480
- }
481
- const comments = context.sourceCode.getCommentsBefore(node);
482
- const isDeprecated = comments.some((comment) => comment.value.includes("@deprecated"));
483
- if (isDeprecated) {
484
- return;
485
- }
486
- const componentName = context.sourceCode.getAncestors(node).find((ancestor) => ancestor.type === AST_NODE_TYPES4.ClassDeclaration)?.id?.name;
487
- if (componentName === void 0) {
488
- return;
489
- }
490
- const propertyName = node.key.type === AST_NODE_TYPES4.Identifier ? node.key.name : node.key.type === AST_NODE_TYPES4.Literal ? node.key.value : void 0;
491
- if (typeof propertyName !== "string") {
492
- return;
493
- }
494
- let eventName = propertyName;
495
- if (eventNamespaces.length > 0) {
496
- const currentNamespace = eventNamespaces.find((namespace) => eventName.startsWith(namespace));
497
- if (currentNamespace === void 0) {
498
- context.report({
499
- messageId: "eventNamespaceError",
500
- data: {
501
- prefixes: eventNamespaces.join(", ")
502
- },
503
- node: node.key
504
- });
505
- return;
506
- }
507
- eventName = eventName.slice(currentNamespace.length);
508
- }
509
- const startsWithInternal = eventName.toLowerCase().startsWith("internal");
510
- if (startsWithInternal || eventName.toLowerCase().startsWith("private")) {
511
- const hasInternalJsDocTag = comments.some((comment) => comment.value.includes("@internal"));
512
- const hasPrivateJsDocTag = comments.some((comment) => comment.value.includes("@private"));
513
- if (!hasInternalJsDocTag && !hasPrivateJsDocTag) {
514
- context.report({
515
- messageId: "missingPrivateJsDocTag",
516
- node: node.key
517
- });
518
- }
519
- eventName = startsWithInternal ? eventName.slice("internal".length) : eventName.slice("private".length);
520
- }
521
- const capitalIndex = capitalAfterLower.exec(componentName)?.index;
522
- const unNamespacedComponentName = capitalIndex === void 0 ? void 0 : componentName.slice(capitalIndex) || void 0;
523
- const includesComponentName = eventName.startsWith(componentName) || unNamespacedComponentName !== void 0 && eventName.startsWith(unNamespacedComponentName);
524
- if (includeComponentNameInEventName && !includesComponentName) {
525
- context.report({
526
- messageId: "noComponentNameInEventError",
527
- node: node.key
528
- });
529
- } else if (!includeComponentNameInEventName && includesComponentName) {
530
- context.report({
531
- messageId: "componentNameInEventError",
532
- node: node.key
533
- });
534
- }
535
- }
536
- };
537
- }
538
- });
539
- var capitalAfterLower = /(?<=[a-z\d])[A-Z]/u;
540
-
541
- // src/plugins/lumina/rules/decorators-context.ts
542
- var description5 = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
543
- var decoratorsContext = createRule({
544
- name: "decorators-context",
545
- meta: {
546
- docs: {
547
- description: description5,
548
- defaultLevel: "error"
549
- },
550
- messages: {
551
- publicApiMustBePublic: `@property(), @method() and createEvent() members must not have private or protected modifier.
552
-
553
- 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`,
554
- noPropertyDecoratorOnMethods: `Methods must not have @property() nor @state() decorator. Did you mean @property() instead?`,
555
- noCombinedPropertyEvent: `Property may either be an event (initialized with createEvent()) or a property (has @property() decorator), but not both`,
556
- noCombinedPropertyState: `Property may either be a state (initialized with @state()) or a property (has @property() decorator), but not both`,
557
- noComputedName: `Computed property names are not allowed as they are not statically analyzable`
558
- },
559
- type: "problem",
560
- schema: [],
561
- fixable: "code"
562
- },
563
- defaultOptions: [],
564
- create(context) {
565
- return {
566
- PropertyDefinition(node) {
567
- const hasPropertyDecorator = hasDecorator(node, "property");
568
- const isPrivateOrProtected = node.accessibility === "private" || node.accessibility === "protected";
569
- if (hasPropertyDecorator && isPrivateOrProtected) {
570
- context.report({ messageId: "publicApiMustBePublic", node });
571
- }
572
- const isLuminaEvent = isCreateEvent(node);
573
- if (isLuminaEvent && isPrivateOrProtected) {
574
- context.report({ messageId: "publicApiMustBePublic", node });
575
- }
576
- if (isLuminaEvent && hasPropertyDecorator) {
577
- context.report({ messageId: "noCombinedPropertyEvent", node });
578
- }
579
- const hasStateDecorator = hasDecorator(node, "state");
580
- if (hasStateDecorator && hasPropertyDecorator) {
581
- context.report({ messageId: "noCombinedPropertyState", node });
582
- }
583
- if (hasPropertyDecorator && node.computed) {
584
- context.report({ messageId: "noComputedName", node });
585
- }
586
- },
587
- MethodDefinition(node) {
588
- const hasMethodDecorator = hasDecorator(node, "method");
589
- const hasPropertyDecorator = hasDecorator(node, "property");
590
- const hasStateDecorator = hasDecorator(node, "state");
591
- const isPrivateOrProtected = node.accessibility === "private" || node.accessibility === "protected";
592
- if (hasMethodDecorator && isPrivateOrProtected) {
593
- context.report({ messageId: "publicApiMustBePublic", node });
594
- }
595
- if (hasMethodDecorator && (hasPropertyDecorator || hasStateDecorator)) {
596
- context.report({ messageId: "noPropertyDecoratorOnMethods", node });
597
- }
598
- if (hasStateDecorator && hasPropertyDecorator) {
599
- context.report({ messageId: "noCombinedPropertyState", node });
600
- }
601
- const isRealMethod = node.kind === "method";
602
- if (isRealMethod && (hasPropertyDecorator || hasStateDecorator)) {
603
- context.report({ messageId: "noPropertyDecoratorOnMethods", node });
604
- }
605
- if ((hasPropertyDecorator || hasMethodDecorator) && node.computed) {
606
- context.report({ messageId: "noComputedName", node });
607
- }
608
- }
609
- };
610
- }
611
- });
612
-
613
- // src/plugins/lumina/rules/member-ordering.ts
614
- import { AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
615
-
616
- // src/plugins/lumina/rules/member-ordering/config.ts
617
- import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
618
- var ordering = [
619
- /*
620
- * Putting static before instance members is a common OOP convention.
621
- * Static members are always evaluated before instance members.
622
- */
623
- "Static Members",
624
- /**
625
- * Put private properties before public, because private properties are
626
- * often used as a default value for public properties.
627
- */
628
- "Private Properties",
629
- /**
630
- * Put private properties before public, because private properties are often
631
- * used as a default value for public properties.
632
- */
633
- "State Properties",
634
- /*
635
- * Properties are the key component API. Also, since default values of
636
- * properties evaluate before the constructor, they should logically be before
637
- * the constructor.
638
- */
639
- "Public Properties",
640
- /*
641
- * Methods should be a supplementary component API, not primary, so are listed
642
- * after properties. At the same time, it is nice to keep public component API
643
- * all in one place.
644
- */
645
- "Public Methods",
646
- /*
647
- * Put events near properties and methods to keep all public component API in
648
- * one place.
649
- */
650
- "Events",
651
- /*
652
- * Lifecycle methods like constructor and connectedCallback are executed
653
- * after all properties have default values, but before any render functions.
654
- */
655
- "Lifecycle",
656
- /*
657
- * Private methods in between lifecycles and rendering as they are often
658
- * called by either of them.
659
- */
660
- "Private Methods",
661
- /*
662
- * Fred:
663
- * The reason rendering methods are at the bottom of my modules because I can
664
- * quickly jump to the bottom find the rendering method and work on it. Less
665
- * scrolling.
666
- * Once a module has been implemented, most of the bugs/enhancements occur in
667
- * the rendering methods.
668
- */
669
- "Rendering"
670
- ];
671
- var getWireframe = () => new Map(ordering.map((region) => [region, []]));
672
- var entries = Object.entries;
673
- var regionMatchers = entries({
674
- "Static Members": (node) => node.type === AST_NODE_TYPES5.StaticBlock || "static" in node && node.static,
675
- "Lifecycle": (_node, name) => lifecyclesSet.has(name),
676
- "Public Methods": (node) => "decorators" in node && hasDecorator(node, "method"),
677
- "Public Properties": (node) => "decorators" in node && hasDecorator(node, "property"),
678
- "State Properties": (node) => "decorators" in node && hasDecorator(node, "state"),
679
- "Events": isEvent,
680
- "Rendering": (_node, name) => name.startsWith("render") || name.startsWith("_render"),
681
- "Private Properties": (node) => node.type === AST_NODE_TYPES5.PropertyDefinition || // We don't yet support public accessor properties
682
- node.type === AST_NODE_TYPES5.AccessorProperty || node.type === AST_NODE_TYPES5.MethodDefinition && (node.kind === "get" || node.kind === "set"),
683
- "Private Methods": () => true
684
- });
685
- var definitelyComponentRegions = /* @__PURE__ */ new Set([
686
- "Rendering",
687
- "State Properties",
688
- "Public Methods",
689
- "Events"
690
- ]);
691
- var regionsArray = regionMatchers.map(([key]) => key);
692
- function isEvent(node, name) {
693
- if (node.type !== AST_NODE_TYPES5.PropertyDefinition) {
694
- return false;
695
- }
696
- if (name === "arcgisPropertyChange") {
697
- return true;
698
- }
699
- const initializer = node.value;
700
- if (initializer?.type !== AST_NODE_TYPES5.CallExpression) {
701
- return false;
702
- }
703
- const callExpression = initializer.callee;
704
- const functionName = callExpression.type === AST_NODE_TYPES5.Identifier ? callExpression.name : callExpression.type === AST_NODE_TYPES5.CallExpression && callExpression.callee.type === AST_NODE_TYPES5.Identifier ? callExpression.callee.name : void 0;
705
- return functionName === "createEvent" || functionName === "reEmitEvent" || functionName === "usePropertyChange";
706
- }
707
- var lifecycles = [
708
- "constructor",
709
- "connectedCallback",
710
- "load",
711
- "shouldUpdate",
712
- // Not an actual lifecycle method, but can be temporary inserted by the codemod
713
- "_shouldUpdate",
714
- "willUpdate",
715
- "update",
716
- "firstUpdated",
717
- "updated",
718
- "loaded",
719
- "disconnectedCallback"
720
- ];
721
- var lifecyclesSet = new Set(lifecycles);
722
- var supportedNodeTypes = {
723
- MethodDefinition: true,
724
- PropertyDefinition: true,
725
- AccessorProperty: true,
726
- StaticBlock: true,
727
- // These should not be present in non-abstract Lumina component classes
728
- TSAbstractAccessorProperty: false,
729
- TSAbstractMethodDefinition: false,
730
- TSAbstractPropertyDefinition: false,
731
- TSIndexSignature: false
732
- };
733
-
734
- // src/plugins/lumina/rules/member-ordering/comments.ts
735
- import { AST_TOKEN_TYPES } from "@typescript-eslint/utils";
736
- function categorizeComments(membersData, sourceCode) {
737
- const categorized = {
738
- header: [],
739
- regionFooter: /* @__PURE__ */ new Map(),
740
- memberLineEnd: /* @__PURE__ */ new Map(),
741
- footer: [],
742
- membersByName: /* @__PURE__ */ new Map()
743
- };
744
- let regionName;
745
- membersData.forEach((memberData, index) => {
746
- const nameNodes = categorized.membersByName.get(memberData.name);
747
- if (nameNodes !== void 0) {
748
- nameNodes.push(memberData);
749
- } else {
750
- categorized.membersByName.set(memberData.name, [memberData]);
751
- }
752
- memberData.comments = handleLineEndComment(memberData.comments, membersData.at(index - 1), categorized);
753
- let newRegionName;
754
- const lastRegionStart = memberData.comments.findLastIndex((comment) => {
755
- const match = parseRegionComment(comment);
756
- const isRegionStart = match !== void 0 && match.end === void 0;
757
- newRegionName = match?.name;
758
- return isRegionStart;
759
- });
760
- if (lastRegionStart !== -1) {
761
- const commentsBeforeNewRegionStart = memberData.comments.slice(0, lastRegionStart);
762
- const previousMember = membersData.at(index - 1);
763
- handleCommentsAroundEndRegion(
764
- commentsBeforeNewRegionStart,
765
- false,
766
- previousMember ?? memberData,
767
- categorized,
768
- regionName
769
- );
770
- memberData.comments = memberData.comments.slice(lastRegionStart + 1);
771
- regionName = newRegionName;
772
- }
773
- const isLastNode = index === membersData.length - 1;
774
- if (isLastNode) {
775
- const trailingComments = sourceCode.getCommentsAfter(memberData.member);
776
- const remainingTrailingComments = handleLineEndComment(trailingComments, memberData, categorized);
777
- const previousMember = membersData.at(index - 1);
778
- handleCommentsAroundEndRegion(
779
- remainingTrailingComments,
780
- true,
781
- previousMember ?? memberData,
782
- categorized,
783
- regionName
784
- );
785
- }
786
- });
787
- return categorized;
788
- }
789
- function handleLineEndComment(comments, previousNode, categorized) {
790
- if (comments.at(0)?.loc.start.line === previousNode?.member.loc.end.line) {
791
- categorized.memberLineEnd.set(previousNode, comments.slice(0, 1));
792
- return comments.slice(1);
793
- }
794
- return comments;
795
- }
796
- function handleCommentsAroundEndRegion(commentsBeforeNewRegionStart, isFooter, lastMember, categorized, regionName) {
797
- if (regionName === void 0) {
798
- const destination = isFooter ? categorized.footer : categorized.header;
799
- destination.push(...commentsBeforeNewRegionStart);
800
- } else {
801
- const firstEndRegion = commentsBeforeNewRegionStart.findIndex(
802
- (comment) => parseRegionComment(comment)?.end !== void 0
803
- );
804
- let previousRegionComments = commentsBeforeNewRegionStart;
805
- if (firstEndRegion !== -1) {
806
- categorized.footer.push(...commentsBeforeNewRegionStart.slice(firstEndRegion + 1));
807
- previousRegionComments = commentsBeforeNewRegionStart.slice(0, firstEndRegion);
808
- }
809
- const existingFooter = categorized.regionFooter.get(regionName);
810
- if (existingFooter === void 0) {
811
- categorized.regionFooter.set(regionName, { comments: Array.from(previousRegionComments), lastMember });
812
- } else {
813
- existingFooter.comments.push(...previousRegionComments);
814
- }
815
- }
816
- }
817
- var normalizeComments = (comments = [], sourceCode, indent = " ") => comments.filter((comment) => parseRegionComment(comment) === void 0).map((comment) => `${indent}${sourceCode.getText(comment)}`).join("\n").replaceAll(reJsApiSectionComment, "");
818
- var join = (left, right, joiner = "\n\n") => left === "" || right === "" ? right || left : `${left}${joiner}${right}`;
819
- var reRegionComment = /^ ?#(?<end>end)?region ?(?<name>[^\n]*)$/u;
820
- var parseRegionComment = (comment) => comment.type === AST_TOKEN_TYPES.Line ? reRegionComment.exec(comment.value)?.groups : void 0;
821
- var reJsApiSectionComment = /^ \/\/ ?-{4,}\n(?: \/\/\s*\n)? \/\/\s+[^\s][^\n]+\n(?: \/\/\s*\n)? \/\/ ?-{4,}/gmu;
822
-
823
- // src/plugins/lumina/rules/member-ordering/normalize.ts
824
- import { AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
825
- function getNormalizedRegions(membersByName) {
826
- const regions = getWireframe();
827
- membersByName.values().forEach((members) => {
828
- const lowestRegionIndex = members.reduce(
829
- (min, { region }) => Math.min(min, regionsArray.indexOf(region)),
830
- Number.POSITIVE_INFINITY
831
- );
832
- if (members.length === 2 && members[0].member.type === AST_NODE_TYPES6.MethodDefinition && members[0].member.kind === "set") {
833
- members.reverse();
834
- }
835
- const regionName = regionsArray[lowestRegionIndex];
836
- regions.get(regionName).push(...members);
837
- });
838
- const normalizedRegions = Array.from(regions).filter(([_region, members]) => members.length > 0).map(
839
- ([region, members]) => [region, sortableRegions.has(region) ? members.sort(sortFunction.bind(void 0, region)) : members]
840
- );
841
- return normalizedRegions;
842
- }
843
- var sortableRegions = /* @__PURE__ */ new Set(["Public Methods", "Events", "Lifecycle"]);
844
- function sortFunction(region, leftMember, rightMember) {
845
- let leftName = leftMember.name;
846
- let rightName = rightMember.name;
847
- if (region === "Lifecycle") {
848
- const compare = lifecycles.indexOf(leftName) - lifecycles.indexOf(rightName);
849
- return compare < 0 ? -1 : 1;
850
- }
851
- if (leftName.startsWith("_")) {
852
- leftName = leftName.slice(1);
853
- }
854
- if (rightName.startsWith("_")) {
855
- rightName = rightName.slice(1);
856
- }
857
- if (leftName < rightName) {
858
- return -1;
859
- } else if (leftName > rightName) {
860
- return 1;
861
- } else {
862
- return 0;
863
- }
864
- }
865
- function reParentOrphanFooterComments(normalizedRegions, regionFooter) {
866
- const finalRegions = new Set(normalizedRegions.map(([region]) => region));
867
- regionFooter.forEach(({ comments, lastMember }, originalRegionName) => {
868
- const regionStillExists = finalRegions.has(originalRegionName);
869
- if (regionStillExists) {
870
- return;
871
- }
872
- const newFooterCommentsParent = lastMember.region;
873
- const existingParentFooter = regionFooter.get(newFooterCommentsParent);
874
- if (existingParentFooter === void 0) {
875
- regionFooter.set(newFooterCommentsParent, { comments, lastMember });
876
- } else {
877
- existingParentFooter.comments.push(...comments);
878
- }
879
- });
880
- }
881
-
882
- // src/plugins/lumina/rules/member-ordering/build.ts
883
- function buildNewBody(normalizedRegions, strayComments, sourceCode) {
884
- const newRegions = normalizedRegions.map(([region, members]) => {
885
- const memberLines = members.map((memberData, index) => {
886
- const { member, comments, name } = memberData;
887
- const commentsString = normalizeComments(comments, sourceCode);
888
- const code = sourceCode.getText(member);
889
- const lineEndComment = normalizeComments(strayComments.memberLineEnd.get(memberData), sourceCode, " ");
890
- const memberString = ` ${code}${lineEndComment}`;
891
- const fullMemberString = join(commentsString, memberString, "\n");
892
- const nextMember = members.at(index + 1);
893
- const isLast = nextMember === void 0;
894
- const isNextSameName = name.length > 0 && name === nextMember?.name;
895
- const separator = isLast ? "" : isNextSameName ? "\n" : "\n\n";
896
- return `${fullMemberString}${separator}`;
897
- });
898
- const footerComments = normalizeComments(strayComments.regionFooter.get(region)?.comments, sourceCode);
899
- const footer2 = join(footerComments, " //#endregion", "\n\n");
900
- return [` //#region ${region}`, memberLines.join(""), footer2].join("\n\n");
901
- });
902
- const header = normalizeComments(strayComments.header, sourceCode);
903
- const footer = normalizeComments(strayComments.footer, sourceCode);
904
- const newBodyContent = join(join(header, newRegions.join("\n\n")), footer);
905
- const newBody = `{
906
- ${newBodyContent}
907
- }`;
908
- return newBody;
909
- }
910
-
911
- // src/plugins/lumina/rules/member-ordering.ts
912
- var baseDescription = `Consistently sort component members`;
913
- var memberOrdering = createRule({
914
- name: "member-ordering",
915
- meta: {
916
- docs: {
917
- description: baseDescription,
918
- defaultLevel: "warn"
919
- },
920
- messages: {
921
- memberOrdering: "Component member ordering is not consistent. Run ESLint autofix to sort members.",
922
- unsupportedElementType: "Unsupported class element type: {{ type }}"
923
- },
924
- type: "layout",
925
- schema: [],
926
- fixable: "whitespace"
927
- },
928
- defaultOptions: [],
929
- create: (context) => ({
930
- ClassDeclaration(component) {
931
- const isNamedExport = component.parent?.type === AST_NODE_TYPES7.ExportNamedDeclaration;
932
- if (!isNamedExport || component.abstract) {
933
- return;
934
- }
935
- const hasExtends = component.superClass !== null;
936
- if (!hasExtends) {
937
- return;
938
- }
939
- const sourceCode = context.sourceCode;
940
- const seenNames = /* @__PURE__ */ new Set();
941
- let currentRegion = "";
942
- let hasWrongOrdering = false;
943
- let isDefinitelyComponent = false;
944
- let hadError = false;
945
- const membersData = component.body.body.map((member) => {
946
- const isSupportedType = supportedNodeTypes[member.type];
947
- if (!isSupportedType) {
948
- hadError = true;
949
- context.report({ node: member, messageId: "unsupportedElementType", data: { type: member.type } });
950
- }
951
- const name = "key" in member ? getName(member) ?? "" : "";
952
- const newRegion = regionMatchers.find(([, rules]) => rules(member, name))[0];
953
- const comments = sourceCode.getCommentsBefore(member);
954
- if (!hasWrongOrdering) {
955
- let wrongOrdering = false;
956
- const previousRegion = currentRegion;
957
- for (const comment of comments) {
958
- const region = parseRegionComment(comment);
959
- if (region === void 0 || region.end !== void 0) {
960
- continue;
961
- }
962
- currentRegion = region.name;
963
- wrongOrdering ||= currentRegion !== newRegion;
964
- }
965
- if (
966
- /*
967
- * The @property()/@method() decorator might be missing from this
968
- * node in case of getter/setter or method overload, while still
969
- * being in correct ordering.
970
- * The logic in rule body is lightweight compared to more
971
- * comprehensive in the autofix to keep common case fast, so we
972
- * ignore possible errors in getter/setters (but those errors would
973
- * still be fixed as long as there is any other error in the file).
974
- */
975
- !seenNames.has(name) && (member.type !== AST_NODE_TYPES7.MethodDefinition || member.kind !== "get" && member.kind !== "set")
976
- ) {
977
- wrongOrdering ||= currentRegion !== newRegion;
978
- wrongOrdering ||= previousRegion !== currentRegion && ordering.indexOf(previousRegion) > ordering.indexOf(currentRegion);
979
- hasWrongOrdering = wrongOrdering;
980
- }
981
- seenNames.add(name);
982
- }
983
- isDefinitelyComponent ||= definitelyComponentRegions.has(newRegion);
984
- return { member, name, comments, region: newRegion };
985
- });
986
- if (!isDefinitelyComponent) {
987
- return;
988
- }
989
- if (hadError) {
990
- return;
991
- }
992
- if (!hasWrongOrdering) {
993
- return;
994
- }
995
- context.report({
996
- /**
997
- * Since this rule is autofixable and stylistic, to be less obtrusive,
998
- * report the error for a single character only, rather than entire
999
- * component
1000
- */
1001
- loc: {
1002
- start: component.loc.end,
1003
- end: component.loc.end
1004
- },
1005
- messageId: "memberOrdering",
1006
- /*
1007
- * We delay as much work as possible till the autofixer is run because
1008
- * most of the time the rule will be run without need for autofixing.
1009
- */
1010
- fix(fixer) {
1011
- const { membersByName, ...strayComments } = categorizeComments(membersData, sourceCode);
1012
- const normalizedRegions = getNormalizedRegions(membersByName);
1013
- reParentOrphanFooterComments(normalizedRegions, strayComments.regionFooter);
1014
- const newBody = buildNewBody(normalizedRegions, strayComments, sourceCode);
1015
- return fixer.replaceText(component.body, newBody);
1016
- }
1017
- });
1018
- }
1019
- })
1020
- });
1021
-
1022
- // src/plugins/lumina/rules/no-ignore-jsdoc-tag.ts
1023
- var description6 = `Use @internal or @private JSDoc tag over @ignore. See https://qawebgis.esri.com/components/lumina/documenting-components#excluding-api-from-public-documentation`;
1024
- var noIgnoreJsDocTag = createRule({
1025
- name: "no-ignore-jsdoc-tag",
1026
- meta: {
1027
- docs: {
1028
- description: description6,
1029
- defaultLevel: "error"
1030
- },
1031
- messages: {
1032
- noIgnoreJsDocTag: description6
1033
- },
1034
- type: "problem",
1035
- schema: []
1036
- },
1037
- defaultOptions: [],
1038
- create(context) {
1039
- const luminaJsxCheck = checkForLuminaJsx();
1040
- return {
1041
- "ImportDeclaration": luminaJsxCheck,
1042
- "Program:exit"() {
1043
- if (!luminaJsxCheck.isLuminaJsx) {
1044
- return;
1045
- }
1046
- Array.from(
1047
- context.sourceCode.text.matchAll(reIgnore),
1048
- (match) => context.report({
1049
- messageId: "noIgnoreJsDocTag",
1050
- loc: {
1051
- start: context.sourceCode.getLocFromIndex(match.index + "* ".length),
1052
- end: context.sourceCode.getLocFromIndex(match.index + match[0].length)
1053
- }
1054
- })
1055
- );
1056
- }
1057
- };
1058
- }
1059
- });
1060
- var reIgnore = /\* @ignore/gu;
1061
-
1062
- // src/plugins/lumina/rules/no-incorrect-dynamic-tag-name.ts
1063
- import { AST_NODE_TYPES as AST_NODE_TYPES8, ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
1064
- import ts2 from "typescript";
1065
- var description7 = `Detect incorrect usage of dynamic JSX tag name`;
1066
- var noIncorrectDynamicTagName = createRule({
1067
- name: "no-incorrect-dynamic-tag-name",
1068
- meta: {
1069
- docs: {
1070
- description: description7,
1071
- defaultLevel: "error"
1072
- },
1073
- messages: {
1074
- 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`
1075
- },
1076
- type: "problem",
1077
- schema: []
1078
- },
1079
- defaultOptions: [],
1080
- create(context) {
1081
- const services = ESLintUtils3.getParserServices(context);
1082
- const luminaJsxCheck = checkForLuminaJsx();
1083
- return {
1084
- ImportDeclaration: luminaJsxCheck,
1085
- JSXIdentifier(node) {
1086
- if (!luminaJsxCheck.isLuminaJsx) {
1087
- return;
1088
- }
1089
- const isInTagName = node.parent?.type !== AST_NODE_TYPES8.JSXAttribute;
1090
- if (!isInTagName) {
1091
- return;
1092
- }
1093
- const isCapitalized = !node.name.startsWith(node.name.charAt(0).toLowerCase());
1094
- if (!isCapitalized) {
1095
- return;
1096
- }
1097
- const isCorrectDynamicTagName = node.name === "DynamicHtmlTag" || node.name === "DynamicSvgTag";
1098
- if (isCorrectDynamicTagName) {
1099
- return;
1100
- }
1101
- const type = services.getTypeAtLocation(node);
1102
- const isStringType = type.flags & ts2.TypeFlags.StringLike;
1103
- if (isStringType) {
1104
- context.report({
1105
- messageId: "incorrectDynamicTagName",
1106
- node
1107
- });
1108
- }
1109
- }
1110
- };
1111
- }
1112
- });
1113
-
1114
- // src/plugins/lumina/rules/no-inline-arrow-in-ref.ts
1115
- import { AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
1116
- var baseDescription2 = `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.`;
1117
- var description8 = `${baseDescription2}
1118
-
1119
- Alternatives: https://qawebgis.esri.com/components/lumina/jsx#refs`;
1120
- var noInlineArrowInRef = createRule({
1121
- name: "no-inline-arrow-in-ref",
1122
- meta: {
1123
- docs: {
1124
- description: baseDescription2,
1125
- defaultLevel: "error"
1126
- },
1127
- messages: {
1128
- errorInlineArrow: description8
1129
- },
1130
- type: "problem",
1131
- schema: []
1132
- },
1133
- defaultOptions: [],
1134
- create(context) {
1135
- const luminaJsxCheck = checkForLuminaJsx();
1136
- return {
1137
- ImportDeclaration: luminaJsxCheck,
1138
- JSXAttribute(node) {
1139
- if (!luminaJsxCheck.isLuminaJsx || node.name.name !== "ref" || node.value?.type !== AST_NODE_TYPES9.JSXExpressionContainer) {
1140
- return;
1141
- }
1142
- const initializer = node.value.expression;
1143
- if (initializer.type !== AST_NODE_TYPES9.ArrowFunctionExpression && initializer.type !== AST_NODE_TYPES9.FunctionExpression && (initializer.type !== AST_NODE_TYPES9.CallExpression || !isBindThisCallee(initializer.callee))) {
1144
- return;
1145
- }
1146
- context.report({
1147
- messageId: "errorInlineArrow",
1148
- node: initializer
1149
- });
1150
- }
1151
- };
1152
- }
1153
- });
1154
-
1155
- // src/plugins/lumina/rules/no-invalid-directives-prop.ts
1156
- import { AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
1157
- var description9 = `directives={} prop value must be an array literal. Documentation: https://qawebgis.esri.com/components/lumina/jsx#lit-directives`;
1158
- var noInvalidDirectivesProp = createRule({
1159
- name: "no-invalid-directives-prop",
1160
- meta: {
1161
- docs: {
1162
- description: description9,
1163
- defaultLevel: "error"
1164
- },
1165
- messages: {
1166
- noInvalidDirectivesProp: description9
1167
- },
1168
- type: "problem",
1169
- schema: []
1170
- },
1171
- defaultOptions: [],
1172
- create(context) {
1173
- const luminaJsxCheck = checkForLuminaJsx();
1174
- return {
1175
- ImportDeclaration: luminaJsxCheck,
1176
- JSXAttribute(node) {
1177
- if (!luminaJsxCheck.isLuminaJsx) {
1178
- return;
1179
- }
1180
- if (node.name.name !== "directives") {
1181
- return;
1182
- }
1183
- const array = node.value?.type === AST_NODE_TYPES10.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES10.ArrayExpression ? node.value.expression : void 0;
1184
- if (array === void 0 || array.elements.includes(null)) {
1185
- context.report({
1186
- messageId: "noInvalidDirectivesProp",
1187
- node: node.value ?? node
1188
- });
1189
- return;
1190
- }
1191
- array.elements.forEach((element) => {
1192
- if (element?.type === AST_NODE_TYPES10.SpreadElement) {
1193
- context.report({
1194
- messageId: "noInvalidDirectivesProp",
1195
- node: element
1196
- });
1197
- }
1198
- });
1199
- }
1200
- };
1201
- }
1202
- });
1203
-
1204
- // src/plugins/lumina/rules/no-jsx-spread.ts
1205
- import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
1206
- var description10 = `This spread syntax is not supported. Alternatives: https://qawebgis.esri.com/components/lumina/jsx#spread-attributes`;
1207
- var noJsxSpread = createRule({
1208
- name: "no-jsx-spread",
1209
- meta: {
1210
- docs: {
1211
- description: description10,
1212
- defaultLevel: "error"
1213
- },
1214
- messages: {
1215
- noJsxSpread: description10
1216
- },
1217
- type: "problem",
1218
- schema: []
1219
- },
1220
- defaultOptions: [],
1221
- create(context) {
1222
- const luminaJsxCheck = checkForLuminaJsx();
1223
- return {
1224
- ImportDeclaration: luminaJsxCheck,
1225
- JSXSpreadAttribute(node) {
1226
- if (!luminaJsxCheck.isLuminaJsx) {
1227
- return;
1228
- }
1229
- const name = node.parent.name;
1230
- if (
1231
- // Spread syntax is allowed in function calls like <this.render {...props} />
1232
- name.type === AST_NODE_TYPES11.JSXIdentifier && // Spread syntax is allowed in functions
1233
- (name.name.toLowerCase() === name.name || name.name === "DynamicHtmlTag" || name.name === "DynamicSvgTag")
1234
- ) {
1235
- context.report({
1236
- messageId: "noJsxSpread",
1237
- node
1238
- });
1239
- }
1240
- }
1241
- };
1242
- }
1243
- });
1244
-
1245
- // src/plugins/lumina/rules/no-listen-in-connected-callback.ts
1246
- import { AST_NODE_TYPES as AST_NODE_TYPES12 } from "@typescript-eslint/utils";
1247
- var baseDescription3 = `Do not call this.listen()/this.listenOn() in connectedCallback.`;
1248
- var description11 = `${baseDescription3}
1249
-
1250
- Instead, call this.listen()/this.listenOn() in constructor(), load() or loaded().
1251
-
1252
- Reason:
1253
- this.listen() automatically creates and cleanups the listener on connect/disconnect.
1254
- Since connectedCallback can be called multiple times, duplicate listeners may be created.`;
1255
- var noListenInConnectedCallback = createRule({
1256
- name: "no-listen-in-connected-callback",
1257
- meta: {
1258
- docs: {
1259
- description: baseDescription3,
1260
- defaultLevel: "error"
1261
- },
1262
- messages: {
1263
- errorListenInConnectedCallback: description11
1264
- },
1265
- type: "problem",
1266
- schema: []
1267
- },
1268
- defaultOptions: [],
1269
- create(context) {
1270
- return {
1271
- CallExpression(node) {
1272
- const isListenCall = node.callee.type === AST_NODE_TYPES12.MemberExpression && node.callee.object.type === AST_NODE_TYPES12.ThisExpression && node.callee.property.type === AST_NODE_TYPES12.Identifier && (node.callee.property.name === "listen" || node.callee.property.name === "listenOn");
1273
- if (!isListenCall) {
1274
- return;
1275
- }
1276
- let hasConnectedCallbackParent = false;
1277
- let currentParent = node.parent;
1278
- while (currentParent) {
1279
- if (currentParent.type === AST_NODE_TYPES12.MethodDefinition) {
1280
- if (currentParent.key.type === AST_NODE_TYPES12.Identifier && currentParent.key.name === "connectedCallback") {
1281
- hasConnectedCallbackParent = true;
1282
- }
1283
- break;
1284
- }
1285
- currentParent = currentParent.parent;
1286
- }
1287
- if (!hasConnectedCallbackParent) {
1288
- return;
1289
- }
1290
- context.report({
1291
- messageId: "errorListenInConnectedCallback",
1292
- node
1293
- });
1294
- }
1295
- };
1296
- }
1297
- });
1298
-
1299
- // src/plugins/lumina/rules/no-non-component-exports.ts
1300
- import { AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
1301
- var description12 = `To ensure Hot Module Replacement (HMR) works correctly, the file that defines the Lumina component must not export anything other than the component. Exceptions: type-only exports, and the \`exportsForTests\` object for exposing additional things for usages in tests only`;
1302
- var noNonComponentExports = createRule({
1303
- name: "no-non-component-exports",
1304
- meta: {
1305
- docs: {
1306
- description: description12,
1307
- defaultLevel: "warn"
1308
- },
1309
- messages: {
1310
- noNonComponentExports: description12,
1311
- noDefaultExports: `Default exports are not allowed in files that export Lumina component - only named exports are allowed. ${description12}`,
1312
- noExportAll: `\`export *\` exports are not allowed in files that export Lumina component - only named exports are allowed. ${description12}`
1313
- },
1314
- type: "problem",
1315
- schema: []
1316
- },
1317
- defaultOptions: [],
1318
- create(context) {
1319
- const declaredComponents = /* @__PURE__ */ new Set();
1320
- const hasLuminaDeclarations = context.sourceCode.text.includes("interface DeclareElements");
1321
- return {
1322
- TSModuleDeclaration(node) {
1323
- const luminaDeclarationInterface = extractDeclareElementsInterface(node);
1324
- if (luminaDeclarationInterface === void 0) {
1325
- return;
1326
- }
1327
- luminaDeclarationInterface.body.body.forEach((member) => {
1328
- if (member.type !== AST_NODE_TYPES13.TSPropertySignature || member.computed) {
1329
- return;
1330
- }
1331
- const type = member.typeAnnotation?.typeAnnotation;
1332
- if (type?.type !== AST_NODE_TYPES13.TSTypeReference || type.typeName.type !== AST_NODE_TYPES13.Identifier) {
1333
- return;
1334
- }
1335
- const className = type.typeName.name;
1336
- declaredComponents.add(className);
1337
- });
1338
- },
1339
- ExportNamedDeclaration(node) {
1340
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1341
- return;
1342
- }
1343
- if (node.exportKind === "type") {
1344
- return;
1345
- }
1346
- if (node.declaration?.type === AST_NODE_TYPES13.VariableDeclaration) {
1347
- const isExportsForTests = node.declaration.declarations.every(
1348
- (declaration) => declaration.id.type === AST_NODE_TYPES13.Identifier && declaration.id.name === "exportsForTests"
1349
- );
1350
- if (isExportsForTests) {
1351
- return;
1352
- }
1353
- } else if (node.declaration?.type === AST_NODE_TYPES13.FunctionDeclaration) {
1354
- const isExportsForTests = node.declaration.id?.name === "exportsForTests";
1355
- if (isExportsForTests) {
1356
- return;
1357
- }
1358
- } else if (
1359
- // Type-only constructs
1360
- node.declaration?.type === AST_NODE_TYPES13.TSDeclareFunction || node.declaration?.type === AST_NODE_TYPES13.TSInterfaceDeclaration || node.declaration?.type === AST_NODE_TYPES13.TSTypeAliasDeclaration
1361
- ) {
1362
- return;
1363
- } else if (node.declaration?.type === AST_NODE_TYPES13.ClassDeclaration) {
1364
- const isComponent = declaredComponents.has(node.declaration.id?.name ?? "");
1365
- if (isComponent) {
1366
- return;
1367
- }
1368
- }
1369
- context.report({
1370
- node,
1371
- messageId: "noNonComponentExports"
1372
- });
1373
- },
1374
- ExportDefaultDeclaration(node) {
1375
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1376
- return;
1377
- }
1378
- context.report({
1379
- node,
1380
- messageId: "noDefaultExports"
1381
- });
1382
- },
1383
- ExportAllDeclaration(node) {
1384
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1385
- return;
1386
- }
1387
- if (node.exportKind === "type") {
1388
- return;
1389
- }
1390
- context.report({
1391
- node,
1392
- messageId: "noExportAll"
1393
- });
1394
- }
1395
- };
1396
- }
1397
- });
1398
-
1399
- // src/plugins/lumina/rules/no-property-name-start-with-on.ts
1400
- import { AST_NODE_TYPES as AST_NODE_TYPES14 } from "@typescript-eslint/utils";
1401
- var description13 = `Do not start public property names with "on" as that can confuse frameworks into thinking this property is an event`;
1402
- var noPropertyNameStartWithOn = createRule({
1403
- name: "no-property-name-start-with-on",
1404
- meta: {
1405
- docs: {
1406
- description: description13,
1407
- defaultLevel: "error"
1408
- },
1409
- messages: {
1410
- noPropertyNameStartWithOn: description13
1411
- },
1412
- type: "problem",
1413
- schema: []
1414
- },
1415
- defaultOptions: [],
1416
- create(context) {
1417
- return {
1418
- PropertyDefinition(node) {
1419
- const isPublicProperty = hasDecorator(node, "property");
1420
- if (!isPublicProperty) {
1421
- return;
1422
- }
1423
- const propertyName = node.key.type === AST_NODE_TYPES14.Identifier ? node.key.name : node.key.type === AST_NODE_TYPES14.Literal ? node.key.value : void 0;
1424
- if (typeof propertyName !== "string") {
1425
- return;
1426
- }
1427
- if (!propertyName.startsWith("on")) {
1428
- return;
1429
- }
1430
- const comments = context.sourceCode.getCommentsBefore(node);
1431
- const isDeprecated = comments.some((comment) => comment.value.includes("@deprecated"));
1432
- if (isDeprecated) {
1433
- return;
1434
- }
1435
- context.report({
1436
- messageId: "noPropertyNameStartWithOn",
1437
- node: node.key
1438
- });
1439
- }
1440
- };
1441
- }
1442
- });
1443
-
1444
- // src/plugins/lumina/rules/no-render-false.ts
1445
- import { AST_NODE_TYPES as AST_NODE_TYPES15, ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
1446
-
1447
- // src/plugins/lumina/utils/checker.ts
1448
- import ts3 from "typescript";
1449
- var litTemplateResult = "TemplateResult";
1450
- var explicitJsxTypeName = "JsxNode";
1451
- var implicitJsxTypeName = "Element";
1452
- var implicitJsxParentName = "LuminaJsx";
1453
- function isLuminaJsxType(type) {
1454
- for (const declaration of type.aliasSymbol?.declarations ?? []) {
1455
- if (!ts3.isTypeAliasDeclaration(declaration)) {
1456
- continue;
1457
- }
1458
- const typeName = declaration.name.escapedText;
1459
- if (typeName === litTemplateResult || typeName === explicitJsxTypeName) {
1460
- return true;
1461
- }
1462
- if (typeName !== implicitJsxTypeName) {
1463
- continue;
1464
- }
1465
- const grandParent = declaration.parent?.parent;
1466
- if (!ts3.isModuleDeclaration(grandParent)) {
1467
- continue;
1468
- }
1469
- const symbol = grandParent.symbol;
1470
- if (symbol?.escapedName === implicitJsxParentName) {
1471
- return true;
1472
- }
1473
- }
1474
- return false;
1475
- }
1476
-
1477
- // src/plugins/lumina/rules/no-render-false.ts
1478
- var baseDescription4 = `Avoid accidentally rendering "false" to the screen (Lit stringifies booleans, rather than drop them like React/Stencil).`;
1479
- var description14 = `${baseDescription4}
1480
-
1481
- Lumina automatically handles some cases where "false" may get rendered to the screen, but the pattern this code is using is not handled.`;
1482
- var noRenderFalse = createRule({
1483
- name: "no-render-false",
1484
- meta: {
1485
- docs: {
1486
- description: baseDescription4,
1487
- defaultLevel: "warn"
1488
- },
1489
- messages: {
1490
- errorFalseRendered: description14
1491
- },
1492
- type: "problem",
1493
- schema: [],
1494
- fixable: "code"
1495
- },
1496
- defaultOptions: [],
1497
- create(context) {
1498
- const services = ESLintUtils4.getParserServices(context);
1499
- return {
1500
- LogicalExpression(node) {
1501
- if (node.operator !== "&&") {
1502
- return;
1503
- }
1504
- const type = services.getTypeAtLocation(node.right);
1505
- if (!isLuminaJsxType(type)) {
1506
- return;
1507
- }
1508
- if (node.parent.type === AST_NODE_TYPES15.JSXExpressionContainer) {
1509
- return;
1510
- }
1511
- const right = unwrapExpression(node.right);
1512
- if (right.type === AST_NODE_TYPES15.JSXElement) {
1513
- return;
1514
- }
1515
- if (node.parent.type === AST_NODE_TYPES15.LogicalExpression) {
1516
- return;
1517
- }
1518
- context.report({
1519
- messageId: "errorFalseRendered",
1520
- node,
1521
- fix(fixer) {
1522
- const leftText = context.sourceCode.getText(node.left);
1523
- const rightText = context.sourceCode.getText(node.right);
1524
- const replacement = `${leftText} ? ${rightText} : ""`;
1525
- return fixer.replaceText(node, replacement);
1526
- }
1527
- });
1528
- }
1529
- };
1530
- }
1531
- });
1532
-
1533
- // src/plugins/lumina/rules/no-unnecessary-attribute-name.ts
1534
- import { AST_NODE_TYPES as AST_NODE_TYPES16 } from "@typescript-eslint/utils";
1535
- import { camelToKebab } from "@arcgis/components-utils";
1536
- var description15 = 'There is no need for { attribute: "name" } in @property() when attribute name is trivially inferrable from the property name';
1537
- var noUnnecessaryAttributeName = createRule({
1538
- name: "no-unnecessary-attribute-name",
1539
- meta: {
1540
- docs: {
1541
- description: description15,
1542
- defaultLevel: "warn"
1543
- },
1544
- messages: {
1545
- noUnnecessaryAttributeName: description15
1546
- },
1547
- type: "suggestion",
1548
- schema: [],
1549
- fixable: "code"
1550
- },
1551
- defaultOptions: [],
1552
- create(context) {
1553
- return {
1554
- Decorator(decorator) {
1555
- const part = parsePropertyDecorator(decorator);
1556
- if (part === void 0) {
1557
- return;
1558
- }
1559
- const { properties } = part;
1560
- const property = decorator.parent;
1561
- if (property?.type !== AST_NODE_TYPES16.MethodDefinition && property?.type !== AST_NODE_TYPES16.PropertyDefinition) {
1562
- return;
1563
- }
1564
- const attributeOption = getProperty(properties, "attribute");
1565
- const attributeValue = attributeOption?.type === AST_NODE_TYPES16.Literal && typeof attributeOption.value === "string" ? attributeOption.value : void 0;
1566
- if (attributeOption === void 0 || attributeValue === void 0) {
1567
- return;
1568
- }
1569
- const propertyName = getName(property);
1570
- if (propertyName === void 0) {
1571
- return;
1572
- }
1573
- const inferredName = camelToKebab(propertyName);
1574
- if (attributeValue === inferredName) {
1575
- context.report({
1576
- node: attributeOption,
1577
- messageId: "noUnnecessaryAttributeName",
1578
- fix(fixer) {
1579
- const nextToken = context.sourceCode.getTokenAfter(attributeOption.parent);
1580
- const isTrailingComma = nextToken && nextToken.value === ",";
1581
- return isTrailingComma ? fixer.removeRange([attributeOption.parent.range[0], nextToken.range[1]]) : fixer.remove(attributeOption.parent);
1582
- }
1583
- });
1584
- }
1585
- }
1586
- };
1587
- }
1588
- });
1589
-
1590
- // src/plugins/lumina/rules/no-unnecessary-bind-this.ts
1591
- import { AST_NODE_TYPES as AST_NODE_TYPES17 } from "@typescript-eslint/utils";
1592
- var description16 = `.bind(this) is not necessary in Lit's event listener callbacks and ref callbacks as it is handled automatically.`;
1593
- var noUnnecessaryBindThis = createRule({
1594
- name: "no-unnecessary-bind-this",
1595
- meta: {
1596
- docs: {
1597
- description: description16,
1598
- defaultLevel: "warn"
1599
- },
1600
- messages: {
1601
- noUnnecessaryBindThis: description16
1602
- },
1603
- type: "problem",
1604
- schema: [],
1605
- fixable: "code"
1606
- },
1607
- defaultOptions: [],
1608
- create(context) {
1609
- const luminaJsxCheck = checkForLuminaJsx();
1610
- return {
1611
- ImportDeclaration: luminaJsxCheck,
1612
- JSXExpressionContainer(node) {
1613
- if (!luminaJsxCheck.isLuminaJsx || node.parent.type !== AST_NODE_TYPES17.JSXAttribute || node.parent.name.type !== AST_NODE_TYPES17.JSXIdentifier) {
1614
- return;
1615
- }
1616
- const name = node.parent.name.name;
1617
- const isAutoBindable = name.startsWith("on") || name === "ref";
1618
- if (!isAutoBindable) {
1619
- return;
1620
- }
1621
- const expression = node.expression.type === AST_NODE_TYPES17.ChainExpression ? node.expression.expression : node.expression;
1622
- const isCallWithThis = (
1623
- // expression(...)
1624
- expression.type === AST_NODE_TYPES17.CallExpression && // expression(expression)
1625
- expression.arguments.length === 1 && // expression(this)
1626
- expression.arguments[0].type === AST_NODE_TYPES17.ThisExpression
1627
- );
1628
- if (!isCallWithThis) {
1629
- return;
1630
- }
1631
- const callee = expression.callee;
1632
- if (!isBindThisCallee(callee)) {
1633
- return;
1634
- }
1635
- const tagName = context.sourceCode.getAncestors(node.parent).find((ancestor) => ancestor.type === AST_NODE_TYPES17.JSXOpeningElement)?.name;
1636
- const tagNameString = tagName?.type === AST_NODE_TYPES17.JSXIdentifier ? tagName.name : void 0;
1637
- if (tagNameString === void 0) {
1638
- return;
1639
- }
1640
- const isHtmlElement = tagNameString.startsWith(tagNameString.charAt(0).toLowerCase()) || tagNameString === "DynamicHtmlTag" || tagNameString === "DynamicSvgTag";
1641
- if (!isHtmlElement) {
1642
- return;
1643
- }
1644
- context.report({
1645
- messageId: "noUnnecessaryBindThis",
1646
- node: callee.property,
1647
- fix(fixer) {
1648
- return fixer.replaceText(expression, context.sourceCode.getText(callee.object));
1649
- }
1650
- });
1651
- }
1652
- };
1653
- }
1654
- });
1655
-
1656
- // src/plugins/lumina/rules/no-unnecessary-key.ts
1657
- import { AST_NODE_TYPES as AST_NODE_TYPES18 } from "@typescript-eslint/utils";
1658
- var description17 = `In most cases, key={index} is not necessary in Lumina in .map(). Details: https://qawebgis.esri.com/components/lumina/jsx#key-prop`;
1659
- var noUnnecessaryKey = createRule({
1660
- name: "no-unnecessary-key",
1661
- meta: {
1662
- docs: {
1663
- description: description17,
1664
- defaultLevel: "warn"
1665
- },
1666
- messages: {
1667
- noUnnecessaryKey: description17
1668
- },
1669
- type: "suggestion",
1670
- schema: [],
1671
- fixable: "code"
1672
- },
1673
- defaultOptions: [],
1674
- create(context) {
1675
- const luminaJsxCheck = checkForLuminaJsx();
1676
- return {
1677
- ImportDeclaration: luminaJsxCheck,
1678
- JSXExpressionContainer(node) {
1679
- if (!luminaJsxCheck.isLuminaJsx) {
1680
- return;
1681
- }
1682
- const keyValue = node.expression;
1683
- if (keyValue.type !== AST_NODE_TYPES18.Identifier) {
1684
- return;
1685
- }
1686
- const keyAttribute = node.parent;
1687
- if (keyAttribute?.type !== AST_NODE_TYPES18.JSXAttribute || keyAttribute.name.type !== AST_NODE_TYPES18.JSXIdentifier || keyAttribute.name.name !== "key") {
1688
- return;
1689
- }
1690
- const mapArrowFunction = context.sourceCode.getAncestors(keyAttribute).find(
1691
- (ancestor) => ancestor.type === AST_NODE_TYPES18.ArrowFunctionExpression && ancestor.parent?.type === AST_NODE_TYPES18.CallExpression && ancestor.parent?.callee.type === AST_NODE_TYPES18.MemberExpression && ancestor.parent?.callee.property.type === AST_NODE_TYPES18.Identifier && ancestor.parent?.callee.property.name === "map"
1692
- );
1693
- if (mapArrowFunction === void 0) {
1694
- return;
1695
- }
1696
- const keyParameter = mapArrowFunction.params.at(1);
1697
- if (keyParameter?.type !== AST_NODE_TYPES18.Identifier || keyParameter.name !== keyValue.name) {
1698
- return;
1699
- }
1700
- context.report({
1701
- messageId: "noUnnecessaryKey",
1702
- node: keyAttribute,
1703
- fix(fixer) {
1704
- const usesIndexOutsideKey = Array.from(
1705
- context.sourceCode.getText(mapArrowFunction.body).matchAll(new RegExp(`\\b${keyValue.name}\\b`, "ug"))
1706
- ).length > 1;
1707
- const withoutAttribute = fixer.remove(keyAttribute);
1708
- if (usesIndexOutsideKey || mapArrowFunction.params.length > 2) {
1709
- return withoutAttribute;
1710
- } else {
1711
- return [withoutAttribute, fixer.remove(keyParameter)];
1712
- }
1713
- }
1714
- });
1715
- }
1716
- };
1717
- }
1718
- });
1719
-
1720
- // src/plugins/lumina/rules/tag-name-rules.ts
1721
- import { AST_NODE_TYPES as AST_NODE_TYPES19 } from "@typescript-eslint/utils";
1722
- var description18 = `Validate component tag name`;
1723
- var defaultOptions2 = [
1724
- {
1725
- namespaces: ["arcgis-"]
1726
- }
1727
- ];
1728
- var tagNameRules = createRule({
1729
- name: "tag-name-rules",
1730
- meta: {
1731
- docs: {
1732
- description: description18,
1733
- defaultLevel: "error"
1734
- },
1735
- messages: {
1736
- requireNamespace: "Custom element tag names in this project must start with one of the following namespaces: {{namespaces}}",
1737
- noComputedTagName: "Computed tag names are not allowed",
1738
- noNonStringTagName: 'Tag names must be strings like "arcgis-click" rather than numbers',
1739
- noTagNameIdentifier: "Tag name must include a dash",
1740
- unexpectedDeclareElementsEntry: "Unexpected entry in declare elements interface. Expected a property signature",
1741
- unexpectedDeclarationType: "Unexpected declaration type. Expected component class identifier",
1742
- duplicateDeclaration: "The same component may only be assigned to one class name. If you need multiple tag names, consider sub-classing the component",
1743
- missingClassDeclaration: "Missing class declaration",
1744
- unexpectedTypeArguments: "Unexpected type arguments in the component tag name declaration. Type arguments should be specified in the component class declaration",
1745
- reservedTagName: "This is a reserved html element tag name. Declaring components with this name is forbidden by the custom elements specification.",
1746
- duplicateDeclareGlobal: `There should only be a single "declare global" block in a file. Declaring multiple components in a single file is supported, but they should share the same "declare global {" block. Example:
1747
- declare global {
1748
- interface DeclareElements {
1749
- "arcgis-test1": ArcgisTest1;
1750
- "arcgis-test2": ArcgisTest2;
1751
- }
1752
- }`,
1753
- mustSubclass: "Lumina component is required to subclass LitElement or a subclass of LitElement",
1754
- mustNotSubclassHtmlElement: "Lumina component is not allowed to subclass HTMLElement classes directly. Subclass LitElement or a subclass of LitElement instead",
1755
- tagNameClassNameMismatch: 'The custom element tag name "{{tagName}}" does not seem to match the class name it is assigned to: "{{className}}". Make sure tag name and class name use the same characters in the same order (case insensitive, hyphens removed). {{namespaceNotice}}',
1756
- missingHyphen: "Custom element tag names need to include a hyphen",
1757
- lowercaseTagName: "For consistency, custom element tag names should be defined in lowercase",
1758
- invalidTagName: "Potentially invalid custom element name. Expected tag name to match regex: {{regex}}. This regex is stricter than what the custom elements spec allows in order to catch possible typos. Please contact Lumina maintainers if you need to loosen this check."
1759
- },
1760
- type: "problem",
1761
- schema: [
1762
- {
1763
- type: "object",
1764
- properties: {
1765
- namespaces: {
1766
- type: "array",
1767
- items: {
1768
- type: "string"
1769
- }
1770
- }
1771
- },
1772
- additionalProperties: false
1773
- }
1774
- ],
1775
- fixable: "code"
1776
- },
1777
- defaultOptions: defaultOptions2,
1778
- create(context, options) {
1779
- const declaredComponents = /* @__PURE__ */ new Map();
1780
- let seenDeclareGlobal = false;
1781
- return {
1782
- "TSModuleDeclaration"(node) {
1783
- const luminaDeclarationInterface = extractDeclareElementsInterface(node);
1784
- if (luminaDeclarationInterface === void 0) {
1785
- return;
1786
- }
1787
- if (seenDeclareGlobal) {
1788
- context.report({
1789
- messageId: "duplicateDeclareGlobal",
1790
- node
1791
- });
1792
- return;
1793
- }
1794
- seenDeclareGlobal = true;
1795
- luminaDeclarationInterface.body.body.forEach((member) => {
1796
- if (member.type !== AST_NODE_TYPES19.TSPropertySignature) {
1797
- context.report({
1798
- messageId: "unexpectedDeclareElementsEntry",
1799
- node: member
1800
- });
1801
- return;
1802
- }
1803
- if (member.computed) {
1804
- context.report({
1805
- messageId: "noComputedTagName",
1806
- node: member.key
1807
- });
1808
- return;
1809
- }
1810
- if (member.key.type === AST_NODE_TYPES19.Identifier) {
1811
- context.report({
1812
- messageId: "noTagNameIdentifier",
1813
- node: member.key
1814
- });
1815
- return;
1816
- }
1817
- if (typeof member.key.value !== "string") {
1818
- context.report({
1819
- messageId: "noNonStringTagName",
1820
- node: member.key
1821
- });
1822
- return;
1823
- }
1824
- const tagName = member.key.value;
1825
- let namespaceFreeTagName = tagName;
1826
- const namespaces = options[0].namespaces;
1827
- if (namespaces.length > 0) {
1828
- const namespace = namespaces.find((ns) => tagName.startsWith(ns));
1829
- if (namespace === void 0) {
1830
- context.report({
1831
- messageId: "requireNamespace",
1832
- node: member.key,
1833
- data: {
1834
- namespaces: namespaces.join(", ")
1835
- }
1836
- });
1837
- return;
1838
- } else {
1839
- namespaceFreeTagName = tagName.slice(namespace.length);
1840
- }
1841
- }
1842
- if (!tagName.includes("-")) {
1843
- context.report({
1844
- messageId: "missingHyphen",
1845
- node: member.key
1846
- });
1847
- return;
1848
- }
1849
- if (tagName.toLowerCase() !== tagName) {
1850
- context.report({
1851
- messageId: "lowercaseTagName",
1852
- node: member.key
1853
- });
1854
- return;
1855
- }
1856
- if (!reCustomElementName.test(tagName)) {
1857
- context.report({
1858
- messageId: "invalidTagName",
1859
- node: member.key,
1860
- data: {
1861
- regex: reCustomElementName.toString()
1862
- }
1863
- });
1864
- return;
1865
- }
1866
- if (blockListedCustomElementNames.has(tagName)) {
1867
- context.report({
1868
- messageId: "reservedTagName",
1869
- node: member.key
1870
- });
1871
- return;
1872
- }
1873
- const type = member.typeAnnotation?.typeAnnotation;
1874
- if (type?.type !== AST_NODE_TYPES19.TSTypeReference || type.typeName.type !== AST_NODE_TYPES19.Identifier) {
1875
- context.report({
1876
- messageId: "unexpectedDeclarationType",
1877
- node: type ?? member
1878
- });
1879
- return;
1880
- }
1881
- if (type.typeArguments !== void 0) {
1882
- context.report({
1883
- messageId: "unexpectedTypeArguments",
1884
- node: type
1885
- });
1886
- return;
1887
- }
1888
- const className = type.typeName.name;
1889
- const classNameFromTagName = tagNameToNormalizedClassName(tagName);
1890
- const classNameFromShortTagName = tagNameToNormalizedClassName(namespaceFreeTagName);
1891
- if (classNameFromTagName !== className.toLowerCase() && classNameFromShortTagName !== className.toLowerCase()) {
1892
- context.report({
1893
- messageId: "tagNameClassNameMismatch",
1894
- node: type.typeName,
1895
- data: {
1896
- tagName,
1897
- className,
1898
- namespaceNotice: namespaces.length > 0 ? `In addition, the following tag name prefixes are expected: ${namespaces.join(", ")}` : ""
1899
- }
1900
- });
1901
- return;
1902
- }
1903
- if (declaredComponents.has(className)) {
1904
- context.report({
1905
- messageId: "duplicateDeclaration",
1906
- node: type.typeName
1907
- });
1908
- return;
1909
- }
1910
- declaredComponents.set(className, type.typeName);
1911
- });
1912
- },
1913
- "ClassDeclaration"(node) {
1914
- if (node.id !== null && declaredComponents.has(node.id.name)) {
1915
- declaredComponents.delete(node.id.name);
1916
- if (node.superClass === null) {
1917
- context.report({
1918
- messageId: "mustSubclass",
1919
- node
1920
- });
1921
- } else if (node.superClass.type === AST_NODE_TYPES19.Identifier && node.superClass.name.startsWith("HTML") && node.superClass.name.endsWith("Element")) {
1922
- context.report({
1923
- messageId: "mustNotSubclassHtmlElement",
1924
- node
1925
- });
1926
- }
1927
- }
1928
- },
1929
- "Program:exit"() {
1930
- for (const identifier of declaredComponents.values()) {
1931
- context.report({
1932
- messageId: "missingClassDeclaration",
1933
- node: identifier
1934
- });
1935
- }
1936
- }
1937
- };
1938
- }
1939
- });
1940
- var blockListedCustomElementNames = /* @__PURE__ */ new Set([
1941
- "annotation-xml",
1942
- "color-profile",
1943
- "font-face",
1944
- "font-face-src",
1945
- "font-face-uri",
1946
- "font-face-format",
1947
- "font-face-name",
1948
- "missing-glyph"
1949
- ]);
1950
- var tagNameToNormalizedClassName = (tagName) => tagName.replaceAll("-", "").toLowerCase();
1951
- var reCustomElementName = /^[a-z]+(?:-[a-z\d]+)+$/u;
1952
-
1953
- // src/plugins/lumina/index.ts
1954
- var lumina_default = makeEslintPlugin("lumina", {
1955
- "add-missing-jsx-import": addMissingJsxImport,
1956
- "auto-add-type": autoAddType,
1957
- "component-placement-rules": componentPlacementRules,
1958
- "consistent-event-naming": consistentEventNaming,
1959
- "decorators-context": decoratorsContext,
1960
- "member-ordering": memberOrdering,
1961
- "no-ignore-jsdoc-tag": noIgnoreJsDocTag,
1962
- "no-incorrect-dynamic-tag-name": noIncorrectDynamicTagName,
1963
- "no-inline-arrow-in-ref": noInlineArrowInRef,
1964
- "no-invalid-directives-prop": noInvalidDirectivesProp,
1965
- "no-jsx-spread": noJsxSpread,
1966
- "no-listen-in-connected-callback": noListenInConnectedCallback,
1967
- "no-non-component-exports": noNonComponentExports,
1968
- "no-property-name-start-with-on": noPropertyNameStartWithOn,
1969
- "no-render-false": noRenderFalse,
1970
- "no-unnecessary-attribute-name": noUnnecessaryAttributeName,
1971
- "no-unnecessary-bind-this": noUnnecessaryBindThis,
1972
- "no-unnecessary-key": noUnnecessaryKey,
1973
- "tag-name-rules": tagNameRules
1974
- });
1975
-
1976
- export {
1977
- lumina_default
1978
- };