@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
158 lines (138 loc) • 4.85 kB
JavaScript
/**
* ESLint-Regel: enforce-module-existence
*
* Überprüft, ob importierte Module tatsächlich existieren.
* Verhindert TS2307-Fehler zur Build-Zeit.
*/
import fs from "fs";
import path from "path";
export default {
rules: {
"enforce-module-existence": {
meta: {
type: "problem",
docs: {
description: "Enforce that imported modules actually exist",
category: "Type Safety",
recommended: true,
},
messages: {
moduleNotFound: "Module '{{moduleName}}' not found at path '{{resolvedPath}}'",
cannotResolveModule: "Cannot resolve module '{{moduleName}}'",
},
schema: [
{
type: "object",
properties: {
ignorePatterns: {
type: "array",
items: { type: "string" },
description: "Patterns to ignore",
default: []
}
},
additionalProperties: false
}
]
},
create(context) {
const options = context.options[0] || {};
const ignorePatterns = options.ignorePatterns || [];
/**
* Resolves a module path relative to the current file
*/
function resolveModulePath(moduleName, currentFilePath) {
const projectRoot = context.getCwd();
// Handle @/ imports
if (moduleName.startsWith("@/")) {
const relativePath = moduleName.substring(2);
return path.join(projectRoot, "src", relativePath);
}
// Handle relative imports
if (moduleName.startsWith("./") || moduleName.startsWith("../")) {
const currentDir = path.dirname(currentFilePath);
return path.resolve(currentDir, moduleName);
}
// Handle absolute imports (node_modules)
if (!moduleName.startsWith(".") && !moduleName.startsWith("/")) {
return path.join(projectRoot, "node_modules", moduleName);
}
return null;
}
/**
* Checks if a module exists
*/
function checkModuleExists(moduleName, currentFilePath) {
// Ignore certain patterns
for (const pattern of ignorePatterns) {
if (moduleName.match(new RegExp(pattern))) {
return;
}
}
// Ignore npm packages (don't start with @/ or ./ or ../)
if (!moduleName.startsWith("@/") && !moduleName.startsWith("./") && !moduleName.startsWith("../")) {
return;
}
const resolvedPath = resolveModulePath(moduleName, currentFilePath);
if (!resolvedPath) {
return;
}
// Check for .ts, .tsx, .js, .jsx files
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
for (const ext of extensions) {
const fullPath = resolvedPath + ext;
if (fs.existsSync(fullPath)) {
return;
}
}
// Check if it's a directory with index file
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
const indexPath = path.join(resolvedPath, "index.ts");
if (fs.existsSync(indexPath)) {
return;
}
}
return resolvedPath;
}
return {
ImportDeclaration(node) {
const moduleName = node.source.value;
const currentFilePath = context.getFilename();
const missingPath = checkModuleExists(moduleName, currentFilePath);
if (missingPath) {
context.report({
node: node.source,
messageId: "moduleNotFound",
data: {
moduleName,
resolvedPath: missingPath
}
});
}
},
CallExpression(node) {
// Check dynamic imports
if (node.callee.type === "Import" && node.arguments.length > 0) {
const arg = node.arguments[0];
if (arg.type === "Literal" && typeof arg.value === "string") {
const moduleName = arg.value;
const currentFilePath = context.getFilename();
const missingPath = checkModuleExists(moduleName, currentFilePath);
if (missingPath) {
context.report({
node: arg,
messageId: "moduleNotFound",
data: {
moduleName,
resolvedPath: missingPath
}
});
}
}
}
}
};
}
}
}
};