Skip to content

Commit

Permalink
feat: add support for Import Attributes and RegExp Modifiers (#639)
Browse files Browse the repository at this point in the history
* feat: add support for import attributes and RegExp modifiers

* test: fix test case

* test: add test case to eslint-scope
  • Loading branch information
ota-meshi authored Oct 29, 2024
1 parent 28456b4 commit 2fd4222
Show file tree
Hide file tree
Showing 32 changed files with 4,896 additions and 5 deletions.
185 changes: 185 additions & 0 deletions packages/eslint-scope/tests/import-attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* @fileoverview Tests for ES2025 Import Attributes.
* @author Yosuke Ota
*/

import assert from "node:assert";
import * as espree from "espree";
import { KEYS } from "eslint-visitor-keys";
import { analyze } from "../lib/index.js";

describe("Import Attributes", () => {

describe("const type = \"json\"; import pkg from \"./package.json\" with { type: \"json\" };", () => {
let ast;
let scopeManager;
let globalScope;

beforeEach(() => {
ast = espree.parse("const type = \"json\"; import pkg from \"./package.json\" with { type: \"json\" };", { ecmaVersion: 16, sourceType: "module" });
scopeManager = analyze(ast, { ecmaVersion: 16, sourceType: "module", childVisitorKeys: KEYS });
({ globalScope } = scopeManager);
});

it("the global scope should not have any variables", () => {
assert.strictEqual(globalScope.variables.length, 0);
});

it("the global scope should have one child scope, a module scope", () => {
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "module");
});

it("the module scope should not have any child scopes", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.childScopes.length, 0);
});

it("the module scope should have two variables", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.variables.length, 2);
assert.strictEqual(moduleScope.variables[0].name, "type");
assert.strictEqual(moduleScope.variables[1].name, "pkg");
});

it("the type variable should have one reference, a variable declaration", () => {
const moduleScope = globalScope.childScopes[0];
const typeVariable = moduleScope.variables[0];

assert.strictEqual(typeVariable.references.length, 1);
assert.strictEqual(typeVariable.references[0].identifier, ast.body[0].declarations[0].id);
});
});

describe("const type = \"json\"; export * from \"./package.json\" with { type: \"json\" };", () => {
let ast;
let scopeManager;
let globalScope;

beforeEach(() => {
ast = espree.parse("const type = \"json\"; export * from \"./package.json\" with { type: \"json\" };", { ecmaVersion: 16, sourceType: "module" });
scopeManager = analyze(ast, { ecmaVersion: 16, sourceType: "module", childVisitorKeys: KEYS });
({ globalScope } = scopeManager);
});

it("the global scope should not have any variables", () => {
assert.strictEqual(globalScope.variables.length, 0);
});

it("the global scope should have one child scope, a module scope", () => {
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "module");
});

it("the module scope should not have any child scopes", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.childScopes.length, 0);
});

it("the module scope should have one variable, a type variable", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.variables.length, 1);
assert.strictEqual(moduleScope.variables[0].name, "type");
});

it("the type variable should have one reference, a variable declaration", () => {
const moduleScope = globalScope.childScopes[0];
const typeVariable = moduleScope.variables[0];

assert.strictEqual(typeVariable.references.length, 1);
assert.strictEqual(typeVariable.references[0].identifier, ast.body[0].declarations[0].id);
});
});


describe("const type = \"json\"; export { default } from \"./package.json\" with { type: \"json\" };", () => {
let ast;
let scopeManager;
let globalScope;

beforeEach(() => {
ast = espree.parse("const type = \"json\"; export { default } from \"./package.json\" with { type: \"json\" };", { ecmaVersion: 16, sourceType: "module" });
scopeManager = analyze(ast, { ecmaVersion: 16, sourceType: "module", childVisitorKeys: KEYS });
({ globalScope } = scopeManager);
});

it("the global scope should not have any variables", () => {
assert.strictEqual(globalScope.variables.length, 0);
});

it("the global scope should have one child scope, a module scope", () => {
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "module");
});

it("the module scope should not have any child scopes", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.childScopes.length, 0);
});

it("the module scope should have one variable, a type variable", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.variables.length, 1);
assert.strictEqual(moduleScope.variables[0].name, "type");
});

it("the type variable should have one reference, a variable declaration", () => {
const moduleScope = globalScope.childScopes[0];
const typeVariable = moduleScope.variables[0];

assert.strictEqual(typeVariable.references.length, 1);
assert.strictEqual(typeVariable.references[0].identifier, ast.body[0].declarations[0].id);
});
});


describe("const type = \"json\"; import(\"./package.json\", { with: { type } });", () => {
let ast;
let scopeManager;
let globalScope;

beforeEach(() => {
ast = espree.parse("const type = \"json\"; import(\"./package.json\", { with: { type } });", { ecmaVersion: 16, sourceType: "module" });
scopeManager = analyze(ast, { ecmaVersion: 16, sourceType: "module", childVisitorKeys: KEYS });
({ globalScope } = scopeManager);
});

it("the global scope should not have any variables", () => {
assert.strictEqual(globalScope.variables.length, 0);
});

it("the global scope should have one child scope, a module scope", () => {
assert.strictEqual(globalScope.childScopes.length, 1);
assert.strictEqual(globalScope.childScopes[0].type, "module");
});

it("the module scope should not have any child scopes", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.childScopes.length, 0);
});

it("the module scope should have one variable, a type variable", () => {
const moduleScope = globalScope.childScopes[0];

assert.strictEqual(moduleScope.variables.length, 1);
assert.strictEqual(moduleScope.variables[0].name, "type");
});


it("the type variable should have two references, a variable declaration and import options", () => {
const moduleScope = globalScope.childScopes[0];
const typeVariable = moduleScope.variables[0];

assert.strictEqual(typeVariable.references.length, 2);
assert.strictEqual(typeVariable.references[0].identifier, ast.body[0].declarations[0].id);
assert.strictEqual(typeVariable.references[1].identifier, ast.body[1].expression.options.properties[0].value.properties[0].value);
});
});
});
16 changes: 12 additions & 4 deletions packages/eslint-visitor-keys/lib/visitor-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,17 @@ const KEYS = {
],
ExportAllDeclaration: [
"exported",
"source"
"source",
"attributes"
],
ExportDefaultDeclaration: [
"declaration"
],
ExportNamedDeclaration: [
"declaration",
"specifiers",
"source"
"source",
"attributes"
],
ExportSpecifier: [
"exported",
Expand Down Expand Up @@ -136,15 +138,21 @@ const KEYS = {
"consequent",
"alternate"
],
ImportAttribute: [
"key",
"value"
],
ImportDeclaration: [
"specifiers",
"source"
"source",
"attributes"
],
ImportDefaultSpecifier: [
"local"
],
ImportExpression: [
"source"
"source",
"options"
],
ImportNamespaceSpecifier: [
"local"
Expand Down
2 changes: 1 addition & 1 deletion packages/espree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"funding": "https://opencollective.com/eslint",
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.12.0",
"acorn": "^8.14.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.1.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 41,
"lineNumber": 1,
"column": 42,
"message": "Duplicate attribute key 'type'"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 0,
"lineNumber": 1,
"column": 1,
"message": "'import' and 'export' may appear only with 'sourceType: module'"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo.json" with { type: "json", type: "html" };
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 19,
"lineNumber": 1,
"column": 20,
"message": "Unexpected token ,"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import("foo.json", , );
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 41,
"lineNumber": 1,
"column": 42,
"message": "Unexpected token ,"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 0,
"lineNumber": 1,
"column": 1,
"message": "'import' and 'export' may appear only with 'sourceType: module'"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo.json" with { type: "json", , };
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 27,
"lineNumber": 1,
"column": 28,
"message": "Unexpected token 42"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 0,
"lineNumber": 1,
"column": 1,
"message": "'import' and 'export' may appear only with 'sourceType: module'"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo.json" with { 42: "s" };
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 33,
"lineNumber": 1,
"column": 34,
"message": "Unexpected token 42"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
"index": 0,
"lineNumber": 1,
"column": 1,
"message": "'import' and 'export' may appear only with 'sourceType: module'"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo.json" with { type: 42 };
Loading

0 comments on commit 2fd4222

Please sign in to comment.