Rename no-non-ts-imports to no-asset-imports, allow .js subpaths, and compare node types by enum member
This commit is contained in:
+24
-19
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* `project` — lint rules this repository holds its own source to, on top of
|
* `project` — lint rules this repository holds its own source to, on top of
|
||||||
* the Oxlint categories in `vite.config.ts`. Its rules of its own are `no-disable-comments` and `no-non-ts-imports`.
|
* the Oxlint categories in `vite.config.ts`. Its rules of its own are `no-disable-comments` and `no-asset-imports`.
|
||||||
*
|
*
|
||||||
* Loaded by `vite.config.ts` as an Oxlint `jsPlugins` entry.
|
* Loaded by `vite.config.ts` as an Oxlint `jsPlugins` entry.
|
||||||
*
|
*
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { LanguageOptions, RuleDefinition, RuleVisitor, TextSourceCode } from "@eslint/core";
|
import type { LanguageOptions, RuleDefinition, RuleVisitor, TextSourceCode } from "@eslint/core";
|
||||||
import type { TSESTree } from "@typescript-eslint/types";
|
import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/types";
|
||||||
import type { DummyRuleMap } from "vite-plus/lint";
|
import type { DummyRuleMap } from "vite-plus/lint";
|
||||||
|
|
||||||
/** The source code both hosts hand a rule, with the comment list. */
|
/** The source code both hosts hand a rule, with the comment list. */
|
||||||
@@ -70,12 +70,16 @@ const noDisableCommentsRule: ProjectRule = {
|
|||||||
/** A module specifier read as a path: relative, absolute, or a `#` alias. */
|
/** A module specifier read as a path: relative, absolute, or a `#` alias. */
|
||||||
const PATH_SPECIFIER_PATTERN: RegExp = /^[./#]/u;
|
const PATH_SPECIFIER_PATTERN: RegExp = /^[./#]/u;
|
||||||
|
|
||||||
|
/** Script module extensions an import may name: our `.ts` files and `.js` package subpaths. */
|
||||||
|
const SCRIPT_EXTENSIONS: readonly string[] = [".ts", ".js"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether `source` names something other than a `.ts` module: a query string
|
* Whether `source` names an asset rather than a script module: a query string
|
||||||
* such as `?raw`, or a final path segment with any other extension. Bare
|
* such as `?raw`, or a final path segment with an extension other than `.ts`
|
||||||
* package names are left alone, dots and all, as are extensionless paths.
|
* or `.js`. Bare package names are left alone, dots and all, as are
|
||||||
|
* extensionless paths.
|
||||||
*/
|
*/
|
||||||
function isNonTsSource(source: string): boolean {
|
function isAssetSource(source: string): boolean {
|
||||||
if (source.includes("?")) {
|
if (source.includes("?")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -90,30 +94,31 @@ function isNonTsSource(source: string): boolean {
|
|||||||
if (lastSegment === "." || lastSegment === ".." || !lastSegment.includes(".")) {
|
if (lastSegment === "." || lastSegment === ".." || !lastSegment.includes(".")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !lastSegment.endsWith(".ts");
|
return !SCRIPT_EXTENSIONS.some((extension: string): boolean => lastSegment.endsWith(extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `no-non-ts-imports` — rejects importing anything but a `.ts` module: a
|
* `no-asset-imports` — rejects importing anything but a script module: a
|
||||||
* source path, static or a dynamic `import()` with a string literal, ending in
|
* source path, static or a dynamic `import()` with a string literal, ending in
|
||||||
* another extension (`.css`, `.json`, `.svg`, `.js`, `.tsx`) or carrying a
|
* an extension other than `.ts` or `.js` (`.css`, `.json`, `.svg`, `.tsx`) or
|
||||||
* query string (`?raw`, `?url`), and any import or export with import
|
* carrying a query string (`?raw`, `?url`), and any import or export with
|
||||||
* attributes. Bare package names, `#` aliases, and `node:` builtins without an
|
* import attributes. `.js` covers package subpaths such as
|
||||||
* extension pass; package subpaths are checked like paths.
|
* `lit/decorators.js`. Bare package names, `#` aliases, and `node:` builtins
|
||||||
|
* without an extension pass; package subpaths are checked like paths.
|
||||||
*/
|
*/
|
||||||
const noNonTsImportsRule: ProjectRule = {
|
const noAssetImportsRule: ProjectRule = {
|
||||||
meta: {
|
meta: {
|
||||||
type: "problem",
|
type: "problem",
|
||||||
docs: { description: "Disallow importing modules other than .ts files" },
|
docs: { description: "Disallow importing assets instead of .ts or .js modules" },
|
||||||
schema: [],
|
schema: [],
|
||||||
messages: {
|
messages: {
|
||||||
extension: "Only .ts modules may be imported.",
|
extension: "Only .ts and .js modules may be imported.",
|
||||||
attributes: "Import attributes are not allowed.",
|
attributes: "Import attributes are not allowed.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create(context) {
|
create(context) {
|
||||||
function checkSource(source: TSESTree.StringLiteral): void {
|
function checkSource(source: TSESTree.StringLiteral): void {
|
||||||
if (isNonTsSource(source.value)) {
|
if (isAssetSource(source.value)) {
|
||||||
context.report({ node: source, messageId: "extension" });
|
context.report({ node: source, messageId: "extension" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +141,7 @@ const noNonTsImportsRule: ProjectRule = {
|
|||||||
ExportNamedDeclaration: checkDeclaration,
|
ExportNamedDeclaration: checkDeclaration,
|
||||||
ExportAllDeclaration: checkDeclaration,
|
ExportAllDeclaration: checkDeclaration,
|
||||||
ImportExpression(node: TSESTree.ImportExpression): void {
|
ImportExpression(node: TSESTree.ImportExpression): void {
|
||||||
if (node.source.type === "Literal" && typeof node.source.value === "string") {
|
if (node.source.type === AST_NODE_TYPES.Literal && typeof node.source.value === "string") {
|
||||||
checkSource(node.source);
|
checkSource(node.source);
|
||||||
}
|
}
|
||||||
if (node.options !== null) {
|
if (node.options !== null) {
|
||||||
@@ -203,7 +208,7 @@ const rules: DummyRuleMap = {
|
|||||||
"typescript/no-non-null-assertion": "error",
|
"typescript/no-non-null-assertion": "error",
|
||||||
// No disable comments at all.
|
// No disable comments at all.
|
||||||
"project/no-disable-comments": "error",
|
"project/no-disable-comments": "error",
|
||||||
"project/no-non-ts-imports": "error",
|
"project/no-asset-imports": "error",
|
||||||
"unicorn/no-abusive-eslint-disable": "error",
|
"unicorn/no-abusive-eslint-disable": "error",
|
||||||
// Pedantic, but Node's and the web's own types (`Response`, `Stats`,
|
// Pedantic, but Node's and the web's own types (`Response`, `Stats`,
|
||||||
// `RequestInit`) are mutable, so no parameter of theirs can pass; the
|
// `RequestInit`) are mutable, so no parameter of theirs can pass; the
|
||||||
@@ -219,7 +224,7 @@ const plugin = {
|
|||||||
meta: { name: "project" },
|
meta: { name: "project" },
|
||||||
rules: {
|
rules: {
|
||||||
"no-disable-comments": noDisableCommentsRule,
|
"no-disable-comments": noDisableCommentsRule,
|
||||||
"no-non-ts-imports": noNonTsImportsRule,
|
"no-asset-imports": noAssetImportsRule,
|
||||||
},
|
},
|
||||||
configs: { project: { rules } },
|
configs: { project: { rules } },
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user