@arcgis/eslint-config 5.1.0-next.7 → 5.1.0-next.71

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.
@@ -213,6 +213,18 @@ const defaultConfig = [
213
213
  "@typescript-eslint/restrict-plus-operands": ["warn", { allowNumberAndString: true }],
214
214
  // We often actually have control characters in our regexes
215
215
  "no-control-regex": "off",
216
+ "@typescript-eslint/no-restricted-imports": [
217
+ "error",
218
+ {
219
+ paths: [
220
+ {
221
+ name: "commander",
222
+ importNames: ["Command"],
223
+ message: "Import from @commander-js/extra-typings instead."
224
+ }
225
+ ]
226
+ }
227
+ ],
216
228
  // Functions that deal with JSON.stringify/localStorage may have a type
217
229
  // parameter as a more readable alternative to a type assertion.
218
230
  // Also, this rule is quite complicated/expensive.
@@ -446,7 +458,13 @@ const defaultConfig = [
446
458
  "@typescript-eslint/no-duplicate-type-constituents": ["warn"],
447
459
  "@typescript-eslint/no-unnecessary-boolean-literal-compare": ["warn"],
448
460
  "@typescript-eslint/no-extra-non-null-assertion": ["warn"],
449
- "@typescript-eslint/no-unnecessary-type-arguments": ["warn"],
461
+ // False positive for the following:
462
+ // this.listen<CustomEvent>("arcgisChartsJSDataProcessComplete", this.handleDataProcessComplete);
463
+ "@typescript-eslint/no-unnecessary-type-arguments": "off",
464
+ // The autofix creates TS errors in makeT9nController(). This is a new
465
+ // rule - give it more time to catch the bug cases. Also, this rule
466
+ // changes runtime behavior, and may lead to breaking changes.
467
+ "@typescript-eslint/no-useless-default-assignment": "off",
450
468
  "@typescript-eslint/no-unnecessary-type-assertion": ["warn"],
451
469
  "@typescript-eslint/prefer-includes": ["warn"],
452
470
  "@typescript-eslint/prefer-reduce-type-parameter": ["warn"],
@@ -587,6 +605,18 @@ const defaultConfig = [
587
605
  // Allow empty "files" field to explicitly indicate that no files
588
606
  // should be included in the package
589
607
  "package-json/no-empty-fields": ["error", { ignoreProperties: ["files"] }],
608
+ // We use license instead
609
+ "package-json/require-attribution": "off",
610
+ // We are not open source
611
+ "package-json/require-repository": "off",
612
+ // This option is tricky to set right, especially for web component
613
+ // libraries that have many side effect entrypoints. It can also get out
614
+ // of date easily. If we don't explicitly indicate sideEffects, bundlers
615
+ // have good default AST-based heuristics.
616
+ "package-json/require-sideEffects": "off",
617
+ // Can't address these till 6.0 without breaking changes.
618
+ // Most errors are in react wrappers which will be dropped in 6.0.
619
+ "package-json/require-exports": "off",
590
620
  // Enforce a specific property order for better readability and consistency
591
621
  "package-json/order-properties": [
592
622
  "warn",
@@ -29,6 +29,8 @@ const storybookConfig = [
29
29
  "storybook/no-title-property-in-meta": "off",
30
30
  // Not applicable as we have a central Storybook config (@arcgis/storybook-utils)
31
31
  "storybook/no-uninstalled-addons": "off",
32
+ // Not that helpful as we have a central Storybook config (@arcgis/storybook-utils)
33
+ "storybook/no-renderer-packages": "off",
32
34
  // We never used storiesOf, and it's no longer included in Storybook
33
35
  "storybook/no-stories-of": "off",
34
36
  // Redundant with TypeScript
@@ -0,0 +1,161 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
2
+ const version = "5.1.0-next.71";
3
+ const packageJson = {
4
+ version
5
+ };
6
+ function makeEslintPlugin(pluginName, urlCreator) {
7
+ const rules = [];
8
+ const creator = ESLintUtils.RuleCreator(urlCreator);
9
+ return {
10
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
11
+ createRule(rule) {
12
+ const { meta, name, ...rest } = rule;
13
+ const docs = { ...meta.docs, name };
14
+ const ruleModule = creator({ ...rest, meta: { ...meta, docs }, name });
15
+ rules.push(ruleModule);
16
+ return ruleModule;
17
+ },
18
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
19
+ finalize() {
20
+ const config = {
21
+ rules: Object.fromEntries(
22
+ rules.map((rule) => [`${pluginName}/${rule.meta.docs.name}`, rule.meta.docs.defaultLevel])
23
+ )
24
+ };
25
+ const plugin = {
26
+ meta: { name: `@arcgis/eslint-plugin-${pluginName}`, version: packageJson.version },
27
+ configs: {
28
+ recommended: config
29
+ },
30
+ rules: Object.fromEntries(rules.map((rule) => [rule.meta.docs.name, rule]))
31
+ };
32
+ config.plugins = {
33
+ [pluginName]: plugin
34
+ };
35
+ return plugin;
36
+ }
37
+ };
38
+ }
39
+ const unwrapExpression = (expression) => expression.type === AST_NODE_TYPES.TSAsExpression || expression.type === AST_NODE_TYPES.TSNonNullExpression || expression.type === AST_NODE_TYPES.TSSatisfiesExpression ? unwrapExpression(expression.expression) : expression;
40
+ const luminaEntrypointName = "@arcgis/lumina";
41
+ const luminaTestEntrypointName = "@arcgis/lumina-compiler/testing";
42
+ const luminaJsxExportName = "h";
43
+ function checkForLuminaJsx() {
44
+ const ImportDeclaration = (node) => {
45
+ if (node.source.value !== luminaEntrypointName) {
46
+ return;
47
+ }
48
+ for (const specifier of node.specifiers) {
49
+ 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)) {
50
+ withProperty.isLuminaJsx = true;
51
+ return;
52
+ }
53
+ }
54
+ };
55
+ const withProperty = ImportDeclaration;
56
+ return withProperty;
57
+ }
58
+ function hasDecorator(node, decoratorName) {
59
+ return node.decorators.some(
60
+ (decorator) => decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === decoratorName
61
+ ) ?? false;
62
+ }
63
+ function extractDeclareElementsInterface(node) {
64
+ return node.kind === "global" ? node.body.body.find(
65
+ (node2) => node2.type === AST_NODE_TYPES.TSInterfaceDeclaration && node2.id.name === "DeclareElements"
66
+ ) : void 0;
67
+ }
68
+ function getComponentDeclaration(node) {
69
+ for (const member of node.body.body) {
70
+ if (member.type !== AST_NODE_TYPES.TSPropertySignature) {
71
+ continue;
72
+ }
73
+ const type = member.typeAnnotation?.typeAnnotation;
74
+ if (type?.type !== AST_NODE_TYPES.TSTypeReference || type.typeName.type !== AST_NODE_TYPES.Identifier) {
75
+ continue;
76
+ }
77
+ if (member.key.type !== AST_NODE_TYPES.Literal) {
78
+ continue;
79
+ }
80
+ return member;
81
+ }
82
+ return;
83
+ }
84
+ const sourceCodeDeclaresComponent = (sourceCode) => sourceCode.text.includes("interface DeclareElements");
85
+ function isCreateEvent(node) {
86
+ return node.value?.type === AST_NODE_TYPES.CallExpression && node.value.callee.type === AST_NODE_TYPES.Identifier && node.value.callee.name === "createEvent" && !node.static;
87
+ }
88
+ const getProperty = (properties, name) => properties?.find(
89
+ (option) => option.type === AST_NODE_TYPES.Property && option.key.type === AST_NODE_TYPES.Identifier && option.key.name === name
90
+ )?.value;
91
+ function isGetterWithoutSetter(node) {
92
+ const isGetter = node.type === AST_NODE_TYPES.MethodDefinition && node.kind === "get";
93
+ if (!isGetter) {
94
+ return false;
95
+ }
96
+ const index = node.parent.body.indexOf(node);
97
+ const previousNode = node.parent.body.at(index - 1);
98
+ const nextNode = node.parent.body.at(index + 1);
99
+ const name = getName(node);
100
+ if (name === void 0) {
101
+ return false;
102
+ }
103
+ const previousIsSetter = previousNode?.type === AST_NODE_TYPES.MethodDefinition && previousNode.kind === "set" && getName(previousNode) === name;
104
+ if (previousIsSetter) {
105
+ return false;
106
+ }
107
+ const nextIsSetter = nextNode?.type === AST_NODE_TYPES.MethodDefinition && nextNode.kind === "set" && getName(nextNode) === name;
108
+ return !nextIsSetter;
109
+ }
110
+ function getName(node) {
111
+ if (node.key.type === AST_NODE_TYPES.Identifier) {
112
+ return node.key.name;
113
+ } else if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === "string") {
114
+ return node.key.value;
115
+ } else {
116
+ return void 0;
117
+ }
118
+ }
119
+ function parsePropertyDecorator(decorator) {
120
+ const isPropertyDecorator = decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === "property";
121
+ if (!isPropertyDecorator) {
122
+ return;
123
+ }
124
+ const callExpression = decorator?.expression.type === AST_NODE_TYPES.CallExpression ? decorator.expression : void 0;
125
+ if (callExpression === void 0) {
126
+ return;
127
+ }
128
+ const options = callExpression.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression ? callExpression.arguments[0] : void 0;
129
+ const properties = options?.properties;
130
+ return {
131
+ callExpression,
132
+ options,
133
+ properties
134
+ };
135
+ }
136
+ function isBindThisCallee(callee) {
137
+ return callee.type === AST_NODE_TYPES.MemberExpression && // expression.identifier(this)
138
+ callee.property.type === AST_NODE_TYPES.Identifier && // expression.bind(this)
139
+ callee.property.name === "bind" && // expression.expression.bind(this)
140
+ callee.object.type === AST_NODE_TYPES.MemberExpression && // expression.identifier.bind(this)
141
+ callee.object.property.type === AST_NODE_TYPES.Identifier && // this.identifier.bind(this)
142
+ callee.object.object.type === AST_NODE_TYPES.ThisExpression;
143
+ }
144
+ export {
145
+ luminaEntrypointName as a,
146
+ luminaTestEntrypointName as b,
147
+ getProperty as c,
148
+ isCreateEvent as d,
149
+ extractDeclareElementsInterface as e,
150
+ getName as f,
151
+ getComponentDeclaration as g,
152
+ hasDecorator as h,
153
+ isGetterWithoutSetter as i,
154
+ checkForLuminaJsx as j,
155
+ isBindThisCallee as k,
156
+ luminaJsxExportName as l,
157
+ makeEslintPlugin as m,
158
+ parsePropertyDecorator as p,
159
+ sourceCodeDeclaresComponent as s,
160
+ unwrapExpression as u
161
+ };
@@ -1,4 +1,4 @@
1
- import { m as makeEslintPlugin } from "../../makePlugin-bIDN023m.js";
1
+ import { m as makeEslintPlugin, l as luminaJsxExportName, a as luminaEntrypointName, b as luminaTestEntrypointName, s as sourceCodeDeclaresComponent, p as parsePropertyDecorator, c as getProperty, i as isGetterWithoutSetter, e as extractDeclareElementsInterface, d as isCreateEvent, h as hasDecorator, f as getName, j as checkForLuminaJsx, k as isBindThisCallee, g as getComponentDeclaration, u as unwrapExpression } from "../../estree-IANpebc8.js";
2
2
  import { AST_NODE_TYPES, ESLintUtils, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
3
3
  import ts from "typescript";
4
4
  import { camelToKebab } from "@arcgis/toolkit/string";
@@ -6,105 +6,17 @@ const plugin = makeEslintPlugin(
6
6
  "lumina",
7
7
  (rule) => `https://devtopia.esri.com/WebGIS/arcgis-web-components/tree/main/packages/support-packages/eslint-config/src/plugins/lumina/rules/${rule}.ts`
8
8
  );
9
- const unwrapExpression = (expression) => expression.type === AST_NODE_TYPES.TSAsExpression || expression.type === AST_NODE_TYPES.TSNonNullExpression || expression.type === AST_NODE_TYPES.TSSatisfiesExpression ? unwrapExpression(expression.expression) : expression;
10
- const luminaEntrypointName = "@arcgis/lumina";
11
- const luminaTestEntrypointName = "@arcgis/lumina-compiler/testing";
12
- const luminaJsxExportName = "h";
13
- function checkForLuminaJsx() {
14
- const ImportDeclaration = (node) => {
15
- if (node.source.value !== luminaEntrypointName) {
16
- return;
17
- }
18
- for (const specifier of node.specifiers) {
19
- 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)) {
20
- withProperty.isLuminaJsx = true;
21
- return;
22
- }
23
- }
24
- };
25
- const withProperty = ImportDeclaration;
26
- return withProperty;
27
- }
28
- function hasDecorator(node, decoratorName) {
29
- return node.decorators.some(
30
- (decorator) => decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === decoratorName
31
- ) ?? false;
32
- }
33
- function extractDeclareElementsInterface(node) {
34
- return node.kind === "global" ? node.body.body.find(
35
- (node2) => node2.type === AST_NODE_TYPES.TSInterfaceDeclaration && node2.id.name === "DeclareElements"
36
- ) : void 0;
37
- }
38
- function isCreateEvent(node) {
39
- return node.value?.type === AST_NODE_TYPES.CallExpression && node.value.callee.type === AST_NODE_TYPES.Identifier && node.value.callee.name === "createEvent" && !node.static;
40
- }
41
- const getProperty = (properties, name) => properties?.find(
42
- (option) => option.type === AST_NODE_TYPES.Property && option.key.type === AST_NODE_TYPES.Identifier && option.key.name === name
43
- )?.value;
44
- function isGetterWithoutSetter(node) {
45
- const isGetter = node.type === AST_NODE_TYPES.MethodDefinition && node.kind === "get";
46
- if (!isGetter) {
47
- return false;
48
- }
49
- const index = node.parent.body.indexOf(node);
50
- const previousNode = node.parent.body.at(index - 1);
51
- const nextNode = node.parent.body.at(index + 1);
52
- const name = getName(node);
53
- if (name === void 0) {
54
- return false;
55
- }
56
- const previousIsSetter = previousNode?.type === AST_NODE_TYPES.MethodDefinition && previousNode.kind === "set" && getName(previousNode) === name;
57
- if (previousIsSetter) {
58
- return false;
59
- }
60
- const nextIsSetter = nextNode?.type === AST_NODE_TYPES.MethodDefinition && nextNode.kind === "set" && getName(nextNode) === name;
61
- return !nextIsSetter;
62
- }
63
- function getName(node) {
64
- if (node.key.type === AST_NODE_TYPES.Identifier) {
65
- return node.key.name;
66
- } else if (node.key.type === AST_NODE_TYPES.Literal && typeof node.key.value === "string") {
67
- return node.key.value;
68
- } else {
69
- return void 0;
70
- }
71
- }
72
- function parsePropertyDecorator(decorator) {
73
- const isPropertyDecorator = decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === "property";
74
- if (!isPropertyDecorator) {
75
- return;
76
- }
77
- const callExpression = decorator?.expression.type === AST_NODE_TYPES.CallExpression ? decorator.expression : void 0;
78
- if (callExpression === void 0) {
79
- return;
80
- }
81
- const options = callExpression.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression ? callExpression.arguments[0] : void 0;
82
- const properties = options?.properties;
83
- return {
84
- callExpression,
85
- options,
86
- properties
87
- };
88
- }
89
- function isBindThisCallee(callee) {
90
- return callee.type === AST_NODE_TYPES.MemberExpression && // expression.identifier(this)
91
- callee.property.type === AST_NODE_TYPES.Identifier && // expression.bind(this)
92
- callee.property.name === "bind" && // expression.expression.bind(this)
93
- callee.object.type === AST_NODE_TYPES.MemberExpression && // expression.identifier.bind(this)
94
- callee.object.property.type === AST_NODE_TYPES.Identifier && // this.identifier.bind(this)
95
- callee.object.object.type === AST_NODE_TYPES.ThisExpression;
96
- }
97
9
  const importDeclaration = `import { ${luminaJsxExportName} } from "${luminaEntrypointName}";`;
98
- const description$k = `To use Lumina's JSX, you need to ${importDeclaration}`;
10
+ const description$l = `To use Lumina's JSX, you need to ${importDeclaration}`;
99
11
  plugin.createRule({
100
12
  name: "add-missing-jsx-import",
101
13
  meta: {
102
14
  docs: {
103
- description: description$k,
15
+ description: description$l,
104
16
  defaultLevel: "error"
105
17
  },
106
18
  messages: {
107
- addMissingJsxImport: description$k
19
+ addMissingJsxImport: description$l
108
20
  },
109
21
  type: "problem",
110
22
  schema: [],
@@ -161,12 +73,12 @@ plugin.createRule({
161
73
  };
162
74
  }
163
75
  });
164
- const description$j = "Auto add { type: Boolean } or { type: Number } where necessary";
76
+ const description$k = "Auto add { type: Boolean } or { type: Number } where necessary";
165
77
  plugin.createRule({
166
78
  name: "auto-add-type",
167
79
  meta: {
168
80
  docs: {
169
- description: description$j,
81
+ description: description$k,
170
82
  defaultLevel: "warn"
171
83
  },
172
84
  messages: {
@@ -184,6 +96,9 @@ More information: https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/
184
96
  },
185
97
  defaultOptions: [],
186
98
  create(context) {
99
+ if (!sourceCodeDeclaresComponent(context.sourceCode)) {
100
+ return {};
101
+ }
187
102
  const services = ESLintUtils.getParserServices(context);
188
103
  return {
189
104
  Decorator(decorator) {
@@ -362,6 +277,9 @@ plugin.createRule({
362
277
  type: "problem"
363
278
  },
364
279
  create(context) {
280
+ if (!sourceCodeDeclaresComponent(context.sourceCode)) {
281
+ return {};
282
+ }
365
283
  const bannedEventToMessageLookup = /* @__PURE__ */ new Map();
366
284
  context.options.forEach((option) => {
367
285
  const event = typeof option === "string" ? option : option.event;
@@ -382,32 +300,24 @@ plugin.createRule({
382
300
  });
383
301
  }
384
302
  }
385
- const luminaJsxCheck = checkForLuminaJsx();
386
303
  return {
387
- "ImportDeclaration": luminaJsxCheck,
388
304
  "CallExpression:matches([callee.property.name=addEventListener], [callee.property.name=removeEventListener]), CallExpression[callee.object.type=ThisExpression][callee.property.name=listen]"(node) {
389
- if (!luminaJsxCheck.isLuminaJsx) {
390
- return;
391
- }
392
305
  const eventName = node.arguments[0].value;
393
306
  checkEvent(node, eventName);
394
307
  },
395
308
  "CallExpression[callee.object.type=ThisExpression][callee.property.name=listenOn]"(node) {
396
- if (!luminaJsxCheck.isLuminaJsx) {
397
- return;
398
- }
399
309
  const eventName = node.arguments[1].value;
400
310
  checkEvent(node, eventName);
401
311
  }
402
312
  };
403
313
  }
404
314
  });
405
- const description$i = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
315
+ const description$j = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
406
316
  plugin.createRule({
407
317
  name: "component-placement-rules",
408
318
  meta: {
409
319
  docs: {
410
- description: description$i,
320
+ description: description$j,
411
321
  defaultLevel: "error"
412
322
  },
413
323
  messages: {
@@ -446,7 +356,7 @@ plugin.createRule({
446
356
  };
447
357
  }
448
358
  });
449
- const description$h = `Enforce consistent event naming.`;
359
+ const description$i = `Enforce consistent event naming.`;
450
360
  const defaultOptions$1 = [
451
361
  {
452
362
  eventNamespaces: ["arcgis"],
@@ -457,7 +367,7 @@ plugin.createRule({
457
367
  name: "consistent-event-naming",
458
368
  meta: {
459
369
  docs: {
460
- description: description$h,
370
+ description: description$i,
461
371
  defaultLevel: "warn"
462
372
  },
463
373
  messages: {
@@ -558,12 +468,12 @@ Discussion: https://devtopia.esri.com/WebGIS/arcgis-web-components/discussions/3
558
468
  }
559
469
  });
560
470
  const capitalAfterLower = /(?<=[a-z\d])[A-Z]/u;
561
- const description$g = `Enforce consistent usage of ? for marking property as nullable, rather than |null, |undefined or |Nil.`;
471
+ const description$h = `Enforce consistent usage of ? for marking property as nullable, rather than |null, |undefined or |Nil.`;
562
472
  plugin.createRule({
563
473
  name: "consistent-nullability",
564
474
  meta: {
565
475
  docs: {
566
- description: description$g,
476
+ description: description$h,
567
477
  // TODO: enable this by default
568
478
  defaultLevel: "off"
569
479
  },
@@ -639,12 +549,12 @@ plugin.createRule({
639
549
  };
640
550
  }
641
551
  });
642
- const description$f = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
552
+ const description$g = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
643
553
  plugin.createRule({
644
554
  name: "decorators-context",
645
555
  meta: {
646
556
  docs: {
647
- description: description$f,
557
+ description: description$g,
648
558
  defaultLevel: "error"
649
559
  },
650
560
  messages: {
@@ -662,6 +572,9 @@ If you wish to hide this member from public documentation, use @private or @prot
662
572
  },
663
573
  defaultOptions: [],
664
574
  create(context) {
575
+ if (!sourceCodeDeclaresComponent(context.sourceCode)) {
576
+ return {};
577
+ }
665
578
  return {
666
579
  PropertyDefinition(node) {
667
580
  const hasPropertyDecorator = hasDecorator(node, "property");
@@ -738,7 +651,7 @@ function isLuminaJsxType(type) {
738
651
  }
739
652
  const hasTypeFlag = (type, flag) => type.flags & flag ? true : (type.flags & ts.TypeFlags.Union) !== 0 && type.types.some((t) => hasTypeFlag(t, flag));
740
653
  const literalTypeFlag = ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike;
741
- const description$e = `Need to add explicit type annotation: {{ setterType }}
654
+ const description$f = `Need to add explicit type annotation: {{ setterType }}
742
655
 
743
656
  Explanation:
744
657
  Lumina automatically creates an attribute for a property if property type includes a literal type (string|number|boolean).
@@ -749,11 +662,11 @@ plugin.createRule({
749
662
  name: "explicit-setter-type",
750
663
  meta: {
751
664
  docs: {
752
- description: description$e,
665
+ description: description$f,
753
666
  defaultLevel: "error"
754
667
  },
755
668
  messages: {
756
- explicitSetterType: description$e,
669
+ explicitSetterType: description$f,
757
670
  addExplicitSetterType: `Add {{ setterType }} type annotation`
758
671
  },
759
672
  type: "problem",
@@ -1299,16 +1212,16 @@ function isCreateElementComponent(node) {
1299
1212
  }
1300
1213
  return false;
1301
1214
  }
1302
- const description$d = `Use @internal or @private JSDoc tag over @ignore. See https://webgis.esri.com/references/lumina/documenting-components#excluding-api-from-public-documentation`;
1215
+ const description$e = `Use @internal or @private JSDoc tag over @ignore. See https://webgis.esri.com/references/lumina/documenting-components#excluding-api-from-public-documentation`;
1303
1216
  plugin.createRule({
1304
1217
  name: "no-ignore-jsdoc-tag",
1305
1218
  meta: {
1306
1219
  docs: {
1307
- description: description$d,
1220
+ description: description$e,
1308
1221
  defaultLevel: "error"
1309
1222
  },
1310
1223
  messages: {
1311
- noIgnoreJsDocTag: description$d
1224
+ noIgnoreJsDocTag: description$e
1312
1225
  },
1313
1226
  type: "problem",
1314
1227
  schema: []
@@ -1337,12 +1250,12 @@ plugin.createRule({
1337
1250
  }
1338
1251
  });
1339
1252
  const reIgnore = /\* @ignore/gu;
1340
- const description$c = `Detect incorrect usage of dynamic JSX tag name`;
1253
+ const description$d = `Detect incorrect usage of dynamic JSX tag name`;
1341
1254
  plugin.createRule({
1342
1255
  name: "no-incorrect-dynamic-tag-name",
1343
1256
  meta: {
1344
1257
  docs: {
1345
- description: description$c,
1258
+ description: description$d,
1346
1259
  defaultLevel: "error"
1347
1260
  },
1348
1261
  messages: {
@@ -1385,18 +1298,18 @@ plugin.createRule({
1385
1298
  };
1386
1299
  }
1387
1300
  });
1388
- const description$b = `If inline arrow function is passed to ref, it will be called again on each render.
1301
+ const description$c = `If inline arrow function is passed to ref, it will be called again on each render.
1389
1302
 
1390
1303
  If this is not desirable, see alternatives: https://webgis.esri.com/references/lumina/jsx#refs`;
1391
1304
  plugin.createRule({
1392
1305
  name: "no-inline-arrow-in-ref",
1393
1306
  meta: {
1394
1307
  docs: {
1395
- description: description$b,
1308
+ description: description$c,
1396
1309
  defaultLevel: "warn"
1397
1310
  },
1398
1311
  messages: {
1399
- errorInlineArrow: description$b
1312
+ errorInlineArrow: description$c
1400
1313
  },
1401
1314
  type: "problem",
1402
1315
  schema: []
@@ -1422,6 +1335,50 @@ plugin.createRule({
1422
1335
  };
1423
1336
  }
1424
1337
  });
1338
+ const description$b = `Put @internal and @private on their own JSDoc line, not inline.`;
1339
+ plugin.createRule({
1340
+ name: "no-inline-exposure-jsdoc-tag",
1341
+ meta: {
1342
+ docs: {
1343
+ description: description$b,
1344
+ defaultLevel: "error"
1345
+ },
1346
+ messages: {
1347
+ inlineExposure: description$b
1348
+ },
1349
+ type: "problem",
1350
+ schema: []
1351
+ },
1352
+ defaultOptions: [],
1353
+ create(context) {
1354
+ return {
1355
+ "Program:exit"() {
1356
+ const source = context.sourceCode.text;
1357
+ for (const comment of context.sourceCode.getAllComments()) {
1358
+ const commentText = source.slice(comment.range[0], comment.range[1]);
1359
+ if (!commentText.startsWith("/**")) {
1360
+ continue;
1361
+ }
1362
+ for (const match of comment.value.matchAll(/@(?:internal|private)\b/gu)) {
1363
+ const idx = comment.range[0] + 2 + match.index;
1364
+ const lineStart = source.lastIndexOf("\n", idx - 1) + 1;
1365
+ const before = source.slice(lineStart, idx).trim();
1366
+ if (before === "*" || before === "/**") {
1367
+ continue;
1368
+ }
1369
+ context.report({
1370
+ messageId: "inlineExposure",
1371
+ loc: {
1372
+ start: context.sourceCode.getLocFromIndex(idx - 1),
1373
+ end: context.sourceCode.getLocFromIndex(idx + match[0].length)
1374
+ }
1375
+ });
1376
+ }
1377
+ }
1378
+ }
1379
+ };
1380
+ }
1381
+ });
1425
1382
  const description$a = `directives={} prop value must be an array literal. Documentation: https://webgis.esri.com/references/lumina/jsx#lit-directives`;
1426
1383
  plugin.createRule({
1427
1384
  name: "no-invalid-directives-prop",
@@ -1575,28 +1532,20 @@ plugin.createRule({
1575
1532
  },
1576
1533
  defaultOptions: [],
1577
1534
  create(context) {
1578
- const declaredComponents = /* @__PURE__ */ new Set();
1579
- const hasLuminaDeclarations = context.sourceCode.text.includes("interface DeclareElements");
1535
+ if (!sourceCodeDeclaresComponent(context.sourceCode)) {
1536
+ return {};
1537
+ }
1538
+ let className;
1580
1539
  return {
1581
1540
  TSModuleDeclaration(node) {
1582
1541
  const luminaDeclarationInterface = extractDeclareElementsInterface(node);
1583
1542
  if (luminaDeclarationInterface === void 0) {
1584
1543
  return;
1585
1544
  }
1586
- luminaDeclarationInterface.body.body.forEach((member) => {
1587
- if (member.type !== AST_NODE_TYPES.TSPropertySignature || member.computed) {
1588
- return;
1589
- }
1590
- const type = member.typeAnnotation?.typeAnnotation;
1591
- if (type?.type !== AST_NODE_TYPES.TSTypeReference || type.typeName.type !== AST_NODE_TYPES.Identifier) {
1592
- return;
1593
- }
1594
- const className = type.typeName.name;
1595
- declaredComponents.add(className);
1596
- });
1545
+ className = getComponentDeclaration(luminaDeclarationInterface)?.typeAnnotation?.typeAnnotation.typeName.name;
1597
1546
  },
1598
1547
  ExportNamedDeclaration(node) {
1599
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1548
+ if (className === void 0) {
1600
1549
  return;
1601
1550
  }
1602
1551
  if (node.exportKind === "type") {
@@ -1620,7 +1569,7 @@ plugin.createRule({
1620
1569
  ) {
1621
1570
  return;
1622
1571
  } else if (node.declaration?.type === AST_NODE_TYPES.ClassDeclaration) {
1623
- const isComponent = declaredComponents.has(node.declaration.id?.name ?? "");
1572
+ const isComponent = node.declaration.id?.name === className;
1624
1573
  if (isComponent) {
1625
1574
  return;
1626
1575
  }
@@ -1631,7 +1580,7 @@ plugin.createRule({
1631
1580
  });
1632
1581
  },
1633
1582
  ExportDefaultDeclaration(node) {
1634
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1583
+ if (className === void 0) {
1635
1584
  return;
1636
1585
  }
1637
1586
  context.report({
@@ -1640,7 +1589,7 @@ plugin.createRule({
1640
1589
  });
1641
1590
  },
1642
1591
  ExportAllDeclaration(node) {
1643
- if (!hasLuminaDeclarations && declaredComponents.size === 0) {
1592
+ if (className === void 0) {
1644
1593
  return;
1645
1594
  }
1646
1595
  if (node.exportKind === "type") {
@@ -0,0 +1,2 @@
1
+ declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<"inlineExposure", [], import('../../utils/makePlugin.ts').CommonDocs, import('@typescript-eslint/utils/ts-eslint').RuleListener>;
2
+ export default _default;
@@ -9,6 +9,17 @@ export declare function checkForLuminaJsx(): LuminaJsxCheck & {
9
9
  };
10
10
  export declare function hasDecorator(node: Pick<TSESTree.PropertyDefinitionNonComputedName, "decorators">, decoratorName: string): boolean;
11
11
  export declare function extractDeclareElementsInterface(node: TSESTree.TSModuleDeclaration): TSESTree.TSInterfaceDeclaration | undefined;
12
+ export declare function getComponentDeclaration(node: TSESTree.TSInterfaceDeclaration): {
13
+ key: TSESTree.Literal;
14
+ typeAnnotation: {
15
+ typeAnnotation: {
16
+ typeName: TSESTree.Identifier;
17
+ };
18
+ };
19
+ } | undefined;
20
+ export declare const sourceCodeDeclaresComponent: (sourceCode: {
21
+ readonly text: string;
22
+ }) => boolean;
12
23
  export declare function isCreateEvent(node: TSESTree.PropertyDefinition): boolean;
13
24
  export declare const getProperty: (properties: TSESTree.ObjectLiteralElement[] | undefined, name: string) => TSESTree.Property["value"] | undefined;
14
25
  export declare function isGetterWithoutSetter(node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition): boolean;
@@ -1,4 +1,4 @@
1
- import { m as makeEslintPlugin } from "../../makePlugin-bIDN023m.js";
1
+ import { m as makeEslintPlugin, e as extractDeclareElementsInterface, g as getComponentDeclaration } from "../../estree-IANpebc8.js";
2
2
  import { resolve } from "path/posix";
3
3
  import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
4
  const plugin = makeEslintPlugin(
@@ -7,16 +7,16 @@ const plugin = makeEslintPlugin(
7
7
  );
8
8
  const isTestFile = (filePath) => filePath.includes("/test") || filePath.includes(".test") || filePath.includes(".spec") || filePath.includes("e2e") || filePath.includes("__") || filePath.includes("/.");
9
9
  const isStorybookFile = (filePath) => filePath.includes(".stories");
10
- const description$4 = `Imports of files outside the src/ folder are not-portable and likely to break for consumers of this package.`;
10
+ const description$5 = `Imports of files outside the src/ folder are not-portable and likely to break for consumers of this package.`;
11
11
  plugin.createRule({
12
12
  name: "no-import-outside-src",
13
13
  meta: {
14
14
  docs: {
15
- description: description$4,
15
+ description: description$5,
16
16
  defaultLevel: "error"
17
17
  },
18
18
  messages: {
19
- noImportOutsideSrc: description$4
19
+ noImportOutsideSrc: description$5
20
20
  },
21
21
  type: "problem",
22
22
  schema: []
@@ -50,16 +50,16 @@ plugin.createRule({
50
50
  };
51
51
  }
52
52
  });
53
- const description$3 = `Having two JSDoc comments next to each other is most likely a mistake - consider combining them into one, or separating them for clarity.`;
53
+ const description$4 = `Having two JSDoc comments next to each other is most likely a mistake - consider combining them into one, or separating them for clarity.`;
54
54
  plugin.createRule({
55
55
  name: "no-touching-jsdoc",
56
56
  meta: {
57
57
  docs: {
58
- description: description$3,
58
+ description: description$4,
59
59
  defaultLevel: "warn"
60
60
  },
61
61
  messages: {
62
- noTouchingJsDoc: description$3
62
+ noTouchingJsDoc: description$4
63
63
  },
64
64
  type: "problem",
65
65
  schema: []
@@ -95,17 +95,17 @@ plugin.createRule({
95
95
  }
96
96
  });
97
97
  const reTouchingJsDoc = /\*\/\s+\/\*\*/gu;
98
- const description$2 = `@arcgis/core imports need to end with .js for better compatibility with ESM CDN builds for @arcgis/core and other packages.`;
98
+ const description$3 = `@arcgis/core imports need to end with .js for better compatibility with ESM CDN builds for @arcgis/core and other packages.`;
99
99
  const packagesToEnforce = ["@arcgis/core/", "@amcharts/amcharts4/", "@amcharts/amcharts5/"];
100
100
  plugin.createRule({
101
101
  name: "require-js-in-imports",
102
102
  meta: {
103
103
  docs: {
104
- description: description$2,
104
+ description: description$3,
105
105
  defaultLevel: "warn"
106
106
  },
107
107
  messages: {
108
- requireJsInCoreImport: description$2
108
+ requireJsInCoreImport: description$3
109
109
  },
110
110
  type: "problem",
111
111
  fixable: "code",
@@ -117,7 +117,7 @@ plugin.createRule({
117
117
  return {};
118
118
  }
119
119
  function updateSpecifier(node) {
120
- if (node.source.type !== AST_NODE_TYPES.Literal || node.importKind === "type") {
120
+ if (node.source.type !== AST_NODE_TYPES.Literal) {
121
121
  return;
122
122
  }
123
123
  const specifier = node.source.value;
@@ -139,17 +139,17 @@ plugin.createRule({
139
139
  };
140
140
  }
141
141
  });
142
- const description$1 = "Using .d.ts files is discouraged. Prefer .ts files instead, as they are type-checked and not in global scope.";
142
+ const description$2 = "Using .d.ts files is discouraged. Prefer .ts files instead, as they are type-checked and not in global scope.";
143
143
  const allowedNames = /* @__PURE__ */ new Set(["vite-env.d.ts", "components.d.ts"]);
144
144
  plugin.createRule({
145
145
  name: "no-dts-files",
146
146
  meta: {
147
147
  docs: {
148
- description: description$1,
148
+ description: description$2,
149
149
  defaultLevel: "warn"
150
150
  },
151
151
  messages: {
152
- avoidDtsFiles: description$1
152
+ avoidDtsFiles: description$2
153
153
  },
154
154
  type: "suggestion",
155
155
  schema: []
@@ -179,12 +179,12 @@ plugin.createRule({
179
179
  };
180
180
  }
181
181
  });
182
- const description = `Enforce consistent logging so that ArcGIS developers can easily debug errors or warnings logged by our web components, which may lack a meaningful context in compiled code. See [our documentation on @arcgis/toolkit/log](https://webgis.esri.com/references/toolkit/log).`;
182
+ const description$1 = `Enforce consistent logging so that ArcGIS developers can easily debug errors or warnings logged by our web components, which may lack a meaningful context in compiled code. See [our documentation on @arcgis/toolkit/log](https://webgis.esri.com/references/toolkit/log).`;
183
183
  plugin.createRule({
184
184
  name: "consistent-logging",
185
185
  meta: {
186
186
  docs: {
187
- description,
187
+ description: description$1,
188
188
  defaultLevel: "off"
189
189
  // NOTE: this is turned on conditionally in root eslint config
190
190
  },
@@ -217,6 +217,61 @@ plugin.createRule({
217
217
  };
218
218
  }
219
219
  });
220
+ const description = `Do not use links like [](#something) as they are not portable across .d.ts, and are not validated at build time. Use {@link } syntax, or absolute links. See https://webgis.esri.com/webgis/core/core/documenting-api#link`;
221
+ plugin.createRule({
222
+ name: "no-unsafe-hash-links",
223
+ meta: {
224
+ docs: {
225
+ description,
226
+ defaultLevel: "warn"
227
+ },
228
+ messages: {
229
+ error: description
230
+ },
231
+ type: "problem",
232
+ schema: [],
233
+ fixable: "code"
234
+ },
235
+ defaultOptions: [],
236
+ create(context) {
237
+ let declareElementsInterface;
238
+ return {
239
+ "TSModuleDeclaration"(node) {
240
+ declareElementsInterface = extractDeclareElementsInterface(node);
241
+ },
242
+ "Program:exit"() {
243
+ if (declareElementsInterface === void 0 && !context.sourceCode.text.includes("@public") && !context.sourceCode.text.includes("@internal")) {
244
+ return;
245
+ }
246
+ for (const match of context.sourceCode.text.matchAll(reHashLink)) {
247
+ const fullMatch = match[0];
248
+ const anchor = match[2];
249
+ const replacement = computeReplacement(declareElementsInterface, anchor);
250
+ context.report({
251
+ messageId: "error",
252
+ loc: {
253
+ start: context.sourceCode.getLocFromIndex(match.index),
254
+ end: context.sourceCode.getLocFromIndex(match.index + fullMatch.length)
255
+ },
256
+ fix: replacement === void 0 ? void 0 : (fixer) => fixer.replaceTextRange([match.index, match.index + fullMatch.length], `{@link ${replacement}}`)
257
+ });
258
+ }
259
+ }
260
+ };
261
+ }
262
+ });
263
+ const reHashLink = /\[([^\]\n]+)\]\(#([^)]+)\)/gu;
264
+ function computeReplacement(declareElementsInterface, anchor) {
265
+ if (declareElementsInterface === void 0) {
266
+ return;
267
+ }
268
+ const declaration = getComponentDeclaration(declareElementsInterface);
269
+ if (declaration === void 0) {
270
+ return;
271
+ }
272
+ const tagName = declaration.key.value;
273
+ return `components/${tagName}#${anchor}`;
274
+ }
220
275
  const webgisPlugin = plugin.finalize();
221
276
  export {
222
277
  webgisPlugin
@@ -0,0 +1,2 @@
1
+ declare const _default: import('@typescript-eslint/utils/ts-eslint').RuleModule<"error", [], import('../../utils/makePlugin.ts').CommonDocs, import('@typescript-eslint/utils/ts-eslint').RuleListener>;
2
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ /**
3
+ * A utility for temporary running ESLint with only specific rules enabled.
4
+ *
5
+ * @param config flat config
6
+ * @param enabledRules exceptions
7
+ * @param disableTypeChecking make ESLint much faster by temporary disabling
8
+ * type-aware linting.
9
+ * @returns a new flat config array where every rule is disabled,
10
+ * except for rules explicitly listed in `enabledRules`.
11
+ *
12
+ * @example
13
+ * const config = [
14
+ * // Your ESLint flat config
15
+ * ];
16
+ * export default disableAllRulesExcept(
17
+ * config,
18
+ * {
19
+ * "rule-name-1": "error",
20
+ * },
21
+ * true,
22
+ * );
23
+ */
24
+ export declare function disableAllRulesExcept(config: TSESLint.FlatConfig.ConfigArray, enabledRules: Record<string, TSESLint.FlatConfig.RuleLevel>, disableTypeChecking: boolean): TSESLint.FlatConfig.ConfigArray;
@@ -0,0 +1,47 @@
1
+ function disableAllRulesExcept(config, enabledRules, disableTypeChecking) {
2
+ return config.map((entry) => applyRulesAndOptions(entry, enabledRules, disableTypeChecking));
3
+ }
4
+ function applyRulesAndOptions(entry, enabledRules, disableTypeChecking) {
5
+ if (!entry || typeof entry !== "object") {
6
+ return entry;
7
+ }
8
+ const nextEntry = entry.rules ? {
9
+ ...entry,
10
+ rules: Object.fromEntries(
11
+ Object.keys(entry.rules).map((ruleName) => {
12
+ const override = enabledRules[ruleName];
13
+ return [ruleName, override ?? "off"];
14
+ })
15
+ )
16
+ } : entry;
17
+ return tweakOptions(nextEntry, disableTypeChecking);
18
+ }
19
+ function tweakOptions(entry, disableTypeChecking) {
20
+ if (entry.linterOptions !== void 0) {
21
+ entry = {
22
+ ...entry,
23
+ linterOptions: {
24
+ ...entry.linterOptions,
25
+ reportUnusedDisableDirectives: "off",
26
+ reportUnusedInlineConfigs: "off"
27
+ }
28
+ };
29
+ }
30
+ if (disableTypeChecking && entry.languageOptions?.parserOptions) {
31
+ entry = {
32
+ ...entry,
33
+ languageOptions: {
34
+ ...entry.languageOptions,
35
+ parserOptions: {
36
+ ...entry.languageOptions.parserOptions,
37
+ project: void 0,
38
+ projectService: void 0
39
+ }
40
+ }
41
+ };
42
+ }
43
+ return entry;
44
+ }
45
+ export {
46
+ disableAllRulesExcept
47
+ };
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@arcgis/eslint-config",
3
- "version": "5.1.0-next.7",
4
- "description": "ESLint configuration for arcgis-web-components",
3
+ "version": "5.1.0-next.71",
4
+ "description": "ESLint configuration for WebGIS SDK",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": "./dist/config/index.js",
11
+ "./ts": "./src/config/index.ts",
11
12
  "./application": "./dist/config/applications.js",
12
13
  "./extra": "./dist/config/extra.js",
13
14
  "./lumina": "./dist/config/lumina.js",
15
+ "./lumina/ts": "./src/config/lumina.ts",
14
16
  "./plugins/webgis": "./dist/plugins/webgis/index.js",
15
17
  "./plugins/lumina": "./dist/plugins/lumina/index.js",
18
+ "./utils/disable-rules": "./dist/utils/disable-rules.js",
16
19
  "./package.json": "./package.json"
17
20
  },
18
21
  "files": [
@@ -20,21 +23,21 @@
20
23
  ],
21
24
  "license": "SEE LICENSE IN LICENSE.md",
22
25
  "dependencies": {
23
- "@eslint/js": "^9.39.1",
24
- "@eslint/markdown": "^7.5.1",
26
+ "@eslint/js": "^10.0.1",
27
+ "@eslint/markdown": "^8.0.1",
25
28
  "@types/confusing-browser-globals": "^1.0.3",
26
- "@typescript-eslint/utils": "^8.46.4",
29
+ "@typescript-eslint/utils": "^8.58.0",
27
30
  "confusing-browser-globals": "^1.0.11",
28
- "eslint-plugin-package-json": "^0.88.1",
29
- "eslint-plugin-storybook": "^0.12.0",
31
+ "eslint-plugin-package-json": "~0.91.1",
32
+ "eslint-plugin-storybook": "^10.3.4",
30
33
  "globals": "^16.5.0",
31
- "jsonc-eslint-parser": "^2.0.0",
34
+ "jsonc-eslint-parser": "^3.1.0",
32
35
  "tslib": "^2.8.1",
33
- "typescript": "~5.9.3",
34
- "typescript-eslint": "^8.46.3",
35
- "@arcgis/toolkit": "5.1.0-next.7"
36
+ "typescript": "~6.0.2",
37
+ "typescript-eslint": "^8.58.0",
38
+ "@arcgis/toolkit": "5.1.0-next.71"
36
39
  },
37
40
  "peerDependencies": {
38
- "eslint": "^9.39.1"
41
+ "eslint": "^10.2.0"
39
42
  }
40
43
  }
@@ -1,41 +0,0 @@
1
- import { ESLintUtils } from "@typescript-eslint/utils";
2
- const version = "5.1.0-next.7";
3
- const packageJson = {
4
- version
5
- };
6
- function makeEslintPlugin(pluginName, urlCreator) {
7
- const rules = [];
8
- const creator = ESLintUtils.RuleCreator(urlCreator);
9
- return {
10
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
11
- createRule(rule) {
12
- const { meta, name, ...rest } = rule;
13
- const docs = { ...meta.docs, name };
14
- const ruleModule = creator({ ...rest, meta: { ...meta, docs }, name });
15
- rules.push(ruleModule);
16
- return ruleModule;
17
- },
18
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
19
- finalize() {
20
- const config = {
21
- rules: Object.fromEntries(
22
- rules.map((rule) => [`${pluginName}/${rule.meta.docs.name}`, rule.meta.docs.defaultLevel])
23
- )
24
- };
25
- const plugin = {
26
- meta: { name: `@arcgis/eslint-plugin-${pluginName}`, version: packageJson.version },
27
- configs: {
28
- recommended: config
29
- },
30
- rules: Object.fromEntries(rules.map((rule) => [rule.meta.docs.name, rule]))
31
- };
32
- config.plugins = {
33
- [pluginName]: plugin
34
- };
35
- return plugin;
36
- }
37
- };
38
- }
39
- export {
40
- makeEslintPlugin as m
41
- };