Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 103 additions & 84 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { defineConfig, globalIgnores } from "eslint/config";
import {defineConfig, globalIgnores} from "eslint/config";
import tsParser from "@typescript-eslint/parser";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import react from "eslint-plugin-react";
import lodash from "eslint-plugin-lodash";
import _import from "eslint-plugin-import";
import vitest from "@vitest/eslint-plugin";
import { FlatCompat } from "@eslint/eslintrc";
import {FlatCompat} from "@eslint/eslintrc";
import js from "@eslint/js";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import {fileURLToPath} from "node:url";
import {dirname} from "node:path";
import noMultiImportFromSameSource from "./eslint/rules/noMultiImportFromSameSource.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -32,9 +33,10 @@ export default defineConfig([
languageOptions: {
parser: tsParser,
parserOptions: {
project: resolve(__dirname, "./tsconfig.json"),
//project: resolve(__dirname, "./tsconfig.json"),
project: false,
tsconfigRootDir: __dirname,
sourceType: "module"
sourceType: "module",
},
globals: {
node: true,
Expand All @@ -48,7 +50,12 @@ export default defineConfig([
react,
lodash,
import: _import,
vitest
vitest,
local: {
rules: {
"no-multi-import-from-same-source": noMultiImportFromSameSource
}
}
},
extends: compat.extends(
"plugin:@typescript-eslint/recommended",
Expand Down Expand Up @@ -102,94 +109,106 @@ export default defineConfig([
* Example of code: onOpen && onOpen();
* Basically, this is instead of an if statement.
*/
"@typescript-eslint/no-unused-expressions": "off"
},
settings: {
react: {
version: "18.2.0"
}
}
},
{
files: ["packages/**/*.{js,jsx}"],
languageOptions: {
parserOptions: { sourceType: "module" },
globals: {
node: true,
commonjs: true,
window: true,
document: true
}
},
plugins: {
react,
lodash,
import: _import,
vitest
},
extends: compat.extends(
"plugin:react/recommended"
//"plugin:vitest/recommended"
),
rules: {
"react/prop-types": 0,
"lodash/import-scope": [2, "method"],
"import/no-unresolved": 0,
"@vitest/expect-expect": 0,
"@vitest/no-conditional-expect": 0,
"@vitest/no-commented-out-tests": 0,
"@vitest/no-disabled-tests": 0,
"import/dynamic-import-chunkname": [
2,
{
importFunctions: ["dynamicImport"],
allowEmpty: false
}
],
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["@aws-sdk/*"],
message: "Please use @webiny/aws-sdk instead."
},
{
group: ["@webiny/*/index.*"],
message:
"Do not import index.js/ts/* explicitly. Import the package root instead (e.g. `@webiny/utils`)."
}
]
}
]
"@typescript-eslint/no-unused-expressions": "off",
"local/no-multi-import-from-same-source": "error"
},
settings: {
react: {
version: "18.2.0"
}
}
},
{
files: ["packages/**/*.{js,jsx}"],
languageOptions: {
parserOptions: {sourceType: "module"},
globals: {
node: true,
commonjs: true,
window: true,
document: true
}
},
plugins: {
react,
lodash,
import: _import,
vitest,
local: {
rules: {
"no-multi-import-from-same-source": noMultiImportFromSameSource
}
}
},
extends: compat.extends(
"plugin:react/recommended"
//"plugin:vitest/recommended"
),
rules: {
"react/prop-types": 0,
"lodash/import-scope": [2, "method"],
"import/no-unresolved": 0,
"@vitest/expect-expect": 0,
"@vitest/no-conditional-expect": 0,
"@vitest/no-commented-out-tests": 0,
"@vitest/no-disabled-tests": 0,
"import/dynamic-import-chunkname": [
2,
{
importFunctions: ["dynamicImport"],
allowEmpty: false
}
],
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["@aws-sdk/*"],
message: "Please use @webiny/aws-sdk instead."
},
{
group: ["@webiny/*/index.*"],
message:
"Do not import index.js/ts/* explicitly. Import the package root instead (e.g. `@webiny/utils`)."
}
]
}
],
"local/no-multi-import-from-same-source": "error"
},
settings: {
react: {
version: "18.2.0"
}
}
},
{
files: ["packages/aws-sdk/**/*.{ts,tsx,js,jsx}"],
rules: {
"no-restricted-imports": "off"
}
},
globalIgnores([
".idea/**/*",
".nx/**/*",
".yarn/**/*",
".webiny/**/*",
"**/node_modules/",
"**/dist/",
"**/lib/",
"**/build/",
"**/.out/",
"**/*.d.ts",
"idea.js",
"scripts/**/*.js",
"packages/admin-ui/.storybook/**/*",
//"packages/create-webiny-project/**/*",
"packages/create-webiny-project/_templates/**/*"
])
globalIgnores([
".verdaccio/**",
".swc/**",
".stormTests/**",
".pulumi/**/*",
".webiny/**/*",
".idea/**/*",
".nx/**/*",
".yarn/**/*",
"**/node_modules/",
"node_modules/**",
"coverage/**",
"**/dist/",
"**/lib/",
"**/build/",
"**/.out/",
"**/*.d.ts",
"idea.js",
"scripts/**/*.js",
"packages/admin-ui/.storybook/**/*",
"packages/create-webiny-project/_templates/**/*"
])
]);
106 changes: 106 additions & 0 deletions eslint/rules/noMultiImportFromSameSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
export default {
meta: {
type: "suggestion",
docs: {
description: "Enforce one import per line and preserve type imports with aliases.",
recommended: false
},
fixable: "code",
schema: []
},

create(context) {
const parserServices = context.parserServices;
const checker = parserServices?.program?.getTypeChecker?.() || null;

function isTypeOnlyImport(node, specifier) {
if (!checker || !parserServices?.esTreeNodeToTSNodeMap) {
return false;
}

try {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(specifier);
const symbol = checker.getSymbolAtLocation(tsNode.name);
if (!symbol) {
return false;
}

const declarations = symbol.getDeclarations() || [];
return declarations.every(d => {
const kind = d.kind;
return (
kind === 263 || // InterfaceDeclaration
kind === 264 || // TypeAliasDeclaration
kind === 261 || // EnumDeclaration
kind === 259 || // ClassDeclaration (type only)
kind === 236 // ImportEqualsDeclaration
);
});
} catch {
return false;
}
}

return {
ImportDeclaration(node) {
if (node.specifiers.length <= 1) {
return;
}

const namedSpecifiers = node.specifiers.filter(s => s.type === "ImportSpecifier");
if (namedSpecifiers.length <= 1) {
return;
}

context.report({
node,
message: `Import only one specifier per line from "${node.source.value}".`,
fix(fixer) {
const source = node.source.raw;

const typeImports = [];
const valueImports = [];

for (const s of namedSpecifiers) {
if (isTypeOnlyImport(node, s)) {
typeImports.push(s);
} else {
valueImports.push(s);
}
}

const mkImport = (specifiers, isType) =>
specifiers
.map(s => {
const alias = s.local.name !== s.imported.name ? ` as ${s.local.name}` : "";
return `import ${isType ? "type " : ""}{ ${s.imported.name}${alias} } from ${source};`;
})
.join("\n");

// handle default and namespace imports
const defaultAndNamespace = node.specifiers.filter(
s => s.type !== "ImportSpecifier"
);
const prefix = defaultAndNamespace.length
? `import ${defaultAndNamespace
.map(s => {
if (s.type === "ImportDefaultSpecifier") return s.local.name;
if (s.type === "ImportNamespaceSpecifier") return `* as ${s.local.name}`;
return "";
})
.filter(Boolean)
.join(", ")} from ${source};\n`
: "";

const fixed =
prefix +
(valueImports.length ? mkImport(valueImports, false) + "\n" : "") +
(typeImports.length ? mkImport(typeImports, true) : "");

return fixer.replaceText(node, fixed.trim());
}
});
}
};
}
};
Loading