Skip to content

Commit 3fa009f

Browse files
authored
feat: add support for Import Attributes and RegExp Modifiers (#19076)
* add tests for `astUtils.needsPrecedingSemicolon` * add tests for `prefer-regex-literals` * add tests for `prefer-named-capture-group` * update `id-denylist` * update dependencies * update `id-length` * update `id-match` * update `camelcase` * add tests for `no-underscore-dangle` * refactor `isImportAttributeKey`
1 parent b0faee3 commit 3fa009f

14 files changed

Lines changed: 416 additions & 12 deletions

lib/rules/camelcase.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ module.exports = {
238238
return;
239239
}
240240

241+
/*
242+
* Import attribute keys are always ignored
243+
*/
244+
if (astUtils.isImportAttributeKey(node)) {
245+
return;
246+
}
247+
241248
report(node);
242249
}
243250

@@ -272,7 +279,7 @@ module.exports = {
272279
for (const reference of scope.through) {
273280
const id = reference.identifier;
274281

275-
if (isGoodName(id.name)) {
282+
if (isGoodName(id.name) || astUtils.isImportAttributeKey(id)) {
276283
continue;
277284
}
278285

@@ -326,7 +333,7 @@ module.exports = {
326333
"MethodDefinition > PrivateIdentifier.key",
327334
"PropertyDefinition > PrivateIdentifier.key"
328335
]](node) {
329-
if (properties === "never" || isGoodName(node.name)) {
336+
if (properties === "never" || astUtils.isImportAttributeKey(node) || isGoodName(node.name)) {
330337
return;
331338
}
332339
report(node);

lib/rules/id-denylist.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
"use strict";
88

9+
//------------------------------------------------------------------------------
10+
// Requirements
11+
//------------------------------------------------------------------------------
12+
13+
const astUtils = require("./utils/ast-utils");
14+
915
//------------------------------------------------------------------------------
1016
// Helpers
1117
//------------------------------------------------------------------------------
@@ -154,6 +160,12 @@ module.exports = {
154160
* @returns {boolean} `true` if the node should be checked.
155161
*/
156162
function shouldCheck(node) {
163+
164+
// Import attributes are defined by environments, so naming conventions shouldn't apply to them
165+
if (astUtils.isImportAttributeKey(node)) {
166+
return false;
167+
}
168+
157169
const parent = node.parent;
158170

159171
/*

lib/rules/id-length.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//------------------------------------------------------------------------------
1212

1313
const { getGraphemeCount } = require("../shared/string-utils");
14-
const { getModuleExportName } = require("./utils/ast-utils");
14+
const { getModuleExportName, isImportAttributeKey } = require("./utils/ast-utils");
1515

1616
//------------------------------------------------------------------------------
1717
// Rule Definition
@@ -115,7 +115,7 @@ module.exports = {
115115
isKeyAndValueSame && parent.key === node && properties
116116
);
117117
}
118-
return properties && !parent.computed && parent.key.name === node.name;
118+
return properties && !isImportAttributeKey(node) && !parent.computed && parent.key.name === node.name;
119119
},
120120
ImportSpecifier(parent, node) {
121121
return (

lib/rules/id-match.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55

66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const astUtils = require("./utils/ast-utils");
13+
814
//------------------------------------------------------------------------------
915
// Rule Definition
1016
//------------------------------------------------------------------------------
@@ -180,7 +186,7 @@ module.exports = {
180186
parent = node.parent,
181187
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
182188

183-
if (isReferenceToGlobalVariable(node)) {
189+
if (isReferenceToGlobalVariable(node) || astUtils.isImportAttributeKey(node)) {
184190
return;
185191
}
186192

lib/rules/utils/ast-utils.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ let needsPrecedingSemicolon;
10541054
{
10551055
const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
10561056

1057-
// Declaration types that must contain a string Literal node at the end.
1057+
// Declaration types that cannot be continued by a punctuator when ending with a string Literal that is a direct child.
10581058
const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
10591059

10601060
const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
@@ -1132,6 +1132,48 @@ let needsPrecedingSemicolon;
11321132
};
11331133
}
11341134

1135+
/**
1136+
* Checks if a node is used as an import attribute key, either in a static or dynamic import.
1137+
* @param {ASTNode} node The node to check.
1138+
* @returns {boolean} Whether the node is used as an import attribute key.
1139+
*/
1140+
function isImportAttributeKey(node) {
1141+
const { parent } = node;
1142+
1143+
// static import/re-export
1144+
if (parent.type === "ImportAttribute" && parent.key === node) {
1145+
return true;
1146+
}
1147+
1148+
// dynamic import
1149+
if (
1150+
parent.type === "Property" &&
1151+
!parent.computed &&
1152+
(parent.key === node || parent.value === node && parent.shorthand && !parent.method) &&
1153+
parent.parent.type === "ObjectExpression"
1154+
) {
1155+
const objectExpression = parent.parent;
1156+
const objectExpressionParent = objectExpression.parent;
1157+
1158+
if (
1159+
objectExpressionParent.type === "ImportExpression" &&
1160+
objectExpressionParent.options === objectExpression
1161+
) {
1162+
return true;
1163+
}
1164+
1165+
// nested key
1166+
if (
1167+
objectExpressionParent.type === "Property" &&
1168+
objectExpressionParent.value === objectExpression
1169+
) {
1170+
return isImportAttributeKey(objectExpressionParent.key);
1171+
}
1172+
}
1173+
1174+
return false;
1175+
}
1176+
11351177
//------------------------------------------------------------------------------
11361178
// Public Interface
11371179
//------------------------------------------------------------------------------
@@ -2288,5 +2330,6 @@ module.exports = {
22882330
isTopLevelExpressionStatement,
22892331
isDirective,
22902332
isStartOfExpressionStatement,
2291-
needsPrecedingSemicolon
2333+
needsPrecedingSemicolon,
2334+
isImportAttributeKey
22922335
};

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@
114114
"cross-spawn": "^7.0.2",
115115
"debug": "^4.3.2",
116116
"escape-string-regexp": "^4.0.0",
117-
"eslint-scope": "^8.1.0",
118-
"eslint-visitor-keys": "^4.1.0",
119-
"espree": "^10.2.0",
117+
"eslint-scope": "^8.2.0",
118+
"eslint-visitor-keys": "^4.2.0",
119+
"espree": "^10.3.0",
120120
"esquery": "^1.5.0",
121121
"esutils": "^2.0.2",
122122
"fast-deep-equal": "^3.1.3",

tests/lib/rules/camelcase.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,69 @@ ruleTester.run("camelcase", rule, {
445445
`,
446446
options: [{ properties: "never", ignoreDestructuring: true }],
447447
languageOptions: { ecmaVersion: 2022 }
448+
},
449+
450+
// Import attribute keys
451+
{
452+
code: "import foo from 'foo.json' with { my_type: 'json' }",
453+
options: [{
454+
properties: "always",
455+
ignoreImports: false
456+
}],
457+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
458+
},
459+
{
460+
code: "export * from 'foo.json' with { my_type: 'json' }",
461+
options: [{
462+
properties: "always",
463+
ignoreImports: false
464+
}],
465+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
466+
},
467+
{
468+
code: "export { default } from 'foo.json' with { my_type: 'json' }",
469+
options: [{
470+
properties: "always",
471+
ignoreImports: false
472+
}],
473+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
474+
},
475+
{
476+
code: "import('foo.json', { my_with: { my_type: 'json' } })",
477+
options: [{
478+
properties: "always",
479+
ignoreImports: false
480+
}],
481+
languageOptions: { ecmaVersion: 2025 }
482+
},
483+
{
484+
code: "import('foo.json', { 'with': { my_type: 'json' } })",
485+
options: [{
486+
properties: "always",
487+
ignoreImports: false
488+
}],
489+
languageOptions: { ecmaVersion: 2025 }
490+
},
491+
{
492+
code: "import('foo.json', { my_with: { my_type } })",
493+
options: [{
494+
properties: "always",
495+
ignoreImports: false
496+
}],
497+
languageOptions: { ecmaVersion: 2025 }
498+
},
499+
{
500+
code: "import('foo.json', { my_with: { my_type } })",
501+
options: [{
502+
properties: "always",
503+
ignoreImports: false
504+
}],
505+
languageOptions: {
506+
ecmaVersion: 2025,
507+
globals: {
508+
my_type: true // eslint-disable-line camelcase -- for testing
509+
}
510+
}
448511
}
449512
],
450513
invalid: [
@@ -1519,6 +1582,38 @@ ruleTester.run("camelcase", rule, {
15191582
column: 27
15201583
}
15211584
]
1585+
},
1586+
1587+
// Not an import attribute key
1588+
{
1589+
code: "import('foo.json', { my_with: { [my_type]: 'json' } })",
1590+
options: [{
1591+
properties: "always",
1592+
ignoreImports: false
1593+
}],
1594+
languageOptions: { ecmaVersion: 2025 },
1595+
errors: [
1596+
{
1597+
messageId: "notCamelCase",
1598+
data: { name: "my_type" },
1599+
type: "Identifier"
1600+
}
1601+
]
1602+
},
1603+
{
1604+
code: "import('foo.json', { my_with: { my_type: my_json } })",
1605+
options: [{
1606+
properties: "always",
1607+
ignoreImports: false
1608+
}],
1609+
languageOptions: { ecmaVersion: 2025 },
1610+
errors: [
1611+
{
1612+
messageId: "notCamelCase",
1613+
data: { name: "my_json" },
1614+
type: "Identifier"
1615+
}
1616+
]
15221617
}
15231618
]
15241619
});

tests/lib/rules/id-denylist.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,38 @@ ruleTester.run("id-denylist", rule, {
231231
code: "class C { snake_case; #snake_case; #snake_case2() {} }",
232232
options: ["foo"],
233233
languageOptions: { ecmaVersion: 2022 }
234+
},
235+
236+
// Import attribute keys
237+
{
238+
code: "import foo from 'foo.json' with { type: 'json' }",
239+
options: ["type"],
240+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
241+
},
242+
{
243+
code: "export * from 'foo.json' with { type: 'json' }",
244+
options: ["type"],
245+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
246+
},
247+
{
248+
code: "export { default } from 'foo.json' with { type: 'json' }",
249+
options: ["type"],
250+
languageOptions: { ecmaVersion: 2025, sourceType: "module" }
251+
},
252+
{
253+
code: "import('foo.json', { with: { type: 'json' } })",
254+
options: ["with", "type"],
255+
languageOptions: { ecmaVersion: 2025 }
256+
},
257+
{
258+
code: "import('foo.json', { 'with': { type: 'json' } })",
259+
options: ["type"],
260+
languageOptions: { ecmaVersion: 2025 }
261+
},
262+
{
263+
code: "import('foo.json', { with: { type } })",
264+
options: ["type"],
265+
languageOptions: { ecmaVersion: 2025 }
234266
}
235267
],
236268
invalid: [
@@ -1429,6 +1461,32 @@ ruleTester.run("id-denylist", rule, {
14291461
}
14301462
]
14311463

1464+
},
1465+
1466+
// Not an import attribute key
1467+
{
1468+
code: "import('foo.json', { with: { [type]: 'json' } })",
1469+
options: ["type"],
1470+
languageOptions: { ecmaVersion: 2025 },
1471+
errors: [
1472+
{
1473+
messageId: "restricted",
1474+
data: { name: "type" },
1475+
type: "Identifier"
1476+
}
1477+
]
1478+
},
1479+
{
1480+
code: "import('foo.json', { with: { type: json } })",
1481+
options: ["json"],
1482+
languageOptions: { ecmaVersion: 2025 },
1483+
errors: [
1484+
{
1485+
messageId: "restricted",
1486+
data: { name: "json" },
1487+
type: "Identifier"
1488+
}
1489+
]
14321490
}
14331491
]
14341492
});

0 commit comments

Comments
 (0)