@solid-stack/mason 1.0.34 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +216 -87
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +216 -87
- package/dist/bin.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/core/lintEngine.d.ts +15 -0
- package/dist/core/lintEngine.d.ts.map +1 -1
- package/dist/index.cjs +225 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +225 -65
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lint.d.ts +8 -5
- package/dist/types/lint.d.ts.map +1 -1
- package/dist/utils/config.d.ts +10 -8
- package/dist/utils/config.d.ts.map +1 -1
- package/package.json +1 -1
- package/schema.json +7 -13
package/dist/bin.cjs
CHANGED
|
@@ -218,37 +218,6 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
218
218
|
}
|
|
219
219
|
return path26__default.default.join(projectRoot, "shared");
|
|
220
220
|
}
|
|
221
|
-
function loadLintConfig(projectRoot) {
|
|
222
|
-
const configPath = path26__default.default.join(projectRoot, "mason-lint.config.json");
|
|
223
|
-
if (fs17__default.default.existsSync(configPath)) {
|
|
224
|
-
try {
|
|
225
|
-
return JSON.parse(fs17__default.default.readFileSync(configPath, "utf-8"));
|
|
226
|
-
} catch {
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return {};
|
|
230
|
-
}
|
|
231
|
-
function getResolvedLintPaths(projectRoot, config) {
|
|
232
|
-
const masonConfig = loadMasonConfig(projectRoot);
|
|
233
|
-
const configuredFeatures = config.paths?.features ?? masonConfig?.paths?.features;
|
|
234
|
-
const configuredShared = config.paths?.shared ?? masonConfig?.paths?.shared;
|
|
235
|
-
const configuredErrors = config.paths?.errors;
|
|
236
|
-
const resolveBase = (configuredPath, defaultSrc, defaultRoot) => {
|
|
237
|
-
if (configuredPath !== void 0) {
|
|
238
|
-
return path26__default.default.resolve(projectRoot, configuredPath);
|
|
239
|
-
}
|
|
240
|
-
const srcPath = path26__default.default.join(projectRoot, defaultSrc);
|
|
241
|
-
if (fs17__default.default.existsSync(srcPath)) return srcPath;
|
|
242
|
-
const rootPath = path26__default.default.join(projectRoot, defaultRoot);
|
|
243
|
-
if (fs17__default.default.existsSync(rootPath)) return rootPath;
|
|
244
|
-
return srcPath;
|
|
245
|
-
};
|
|
246
|
-
return {
|
|
247
|
-
featuresBase: resolveBase(configuredFeatures, "src/features", "features"),
|
|
248
|
-
sharedBase: resolveBase(configuredShared, "src/shared", "shared"),
|
|
249
|
-
errorsBase: resolveBase(configuredErrors, "src/errors", "errors")
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
221
|
|
|
253
222
|
// src/utils/project.ts
|
|
254
223
|
function findProjectRoot(startDir = process.cwd()) {
|
|
@@ -4934,19 +4903,43 @@ function scanDirFiles(dir, projectRoot) {
|
|
|
4934
4903
|
}
|
|
4935
4904
|
return files;
|
|
4936
4905
|
}
|
|
4906
|
+
function resolveLintEnvironment(projectRoot) {
|
|
4907
|
+
const config = loadMasonConfig(projectRoot);
|
|
4908
|
+
const resolveDir = (configured, defaultSrc, defaultRoot) => {
|
|
4909
|
+
if (configured !== void 0) {
|
|
4910
|
+
return path26__default.default.resolve(projectRoot, configured);
|
|
4911
|
+
}
|
|
4912
|
+
const srcPath = path26__default.default.join(projectRoot, defaultSrc);
|
|
4913
|
+
if (fs17__default.default.existsSync(srcPath)) return srcPath;
|
|
4914
|
+
const rootPath = path26__default.default.join(projectRoot, defaultRoot);
|
|
4915
|
+
if (fs17__default.default.existsSync(rootPath)) return rootPath;
|
|
4916
|
+
return srcPath;
|
|
4917
|
+
};
|
|
4918
|
+
const featuresBase = resolveDir(config?.paths?.features, "src/features", "features");
|
|
4919
|
+
const sharedBase = resolveDir(config?.paths?.shared, "src/shared", "shared");
|
|
4920
|
+
const errorsBase = resolveDir(config?.paths?.errors, "src/errors", "errors");
|
|
4921
|
+
const defaultBaseErrorRelative = config?.paths?.errors ? path26__default.default.join(config.paths.errors, "AppError.ts") : fs17__default.default.existsSync(path26__default.default.join(projectRoot, "src/errors")) ? "src/errors/AppError.ts" : fs17__default.default.existsSync(path26__default.default.join(projectRoot, "errors")) ? "errors/AppError.ts" : "src/errors/AppError.ts";
|
|
4922
|
+
const baseErrorAbs = path26__default.default.resolve(
|
|
4923
|
+
projectRoot,
|
|
4924
|
+
config?.paths?.baseError || defaultBaseErrorRelative
|
|
4925
|
+
);
|
|
4926
|
+
return { featuresBase, sharedBase, errorsBase, baseErrorAbs };
|
|
4927
|
+
}
|
|
4937
4928
|
function classifyFile(fullPathAbs, featuresBaseAbs, sharedBaseAbs, errorsBaseAbs) {
|
|
4938
4929
|
if (!sharedBaseAbs || !errorsBaseAbs) {
|
|
4939
|
-
const
|
|
4940
|
-
featuresBaseAbs =
|
|
4941
|
-
sharedBaseAbs =
|
|
4942
|
-
errorsBaseAbs =
|
|
4930
|
+
const env = resolveLintEnvironment(featuresBaseAbs);
|
|
4931
|
+
featuresBaseAbs = env.featuresBase;
|
|
4932
|
+
sharedBaseAbs = env.sharedBase;
|
|
4933
|
+
errorsBaseAbs = env.errorsBase;
|
|
4943
4934
|
}
|
|
4944
4935
|
const normFullPath = path26__default.default.resolve(fullPathAbs);
|
|
4945
4936
|
const normErrorsBase = path26__default.default.resolve(errorsBaseAbs);
|
|
4946
4937
|
const normSharedBase = path26__default.default.resolve(sharedBaseAbs);
|
|
4947
4938
|
const normFeaturesBase = path26__default.default.resolve(featuresBaseAbs);
|
|
4948
|
-
const
|
|
4949
|
-
|
|
4939
|
+
const isUnder = (base, target) => {
|
|
4940
|
+
return target === base || target.startsWith(base + path26__default.default.sep);
|
|
4941
|
+
};
|
|
4942
|
+
if (isUnder(normErrorsBase, normFullPath)) {
|
|
4950
4943
|
return {
|
|
4951
4944
|
moduleType: "error",
|
|
4952
4945
|
moduleName: "global-errors",
|
|
@@ -4954,26 +4947,26 @@ function classifyFile(fullPathAbs, featuresBaseAbs, sharedBaseAbs, errorsBaseAbs
|
|
|
4954
4947
|
layer: "domain"
|
|
4955
4948
|
};
|
|
4956
4949
|
}
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
const parts =
|
|
4950
|
+
if (isUnder(normSharedBase, normFullPath)) {
|
|
4951
|
+
const rel = path26__default.default.relative(normSharedBase, normFullPath).replace(/\\/g, "/");
|
|
4952
|
+
const parts = rel.split("/").filter(Boolean);
|
|
4960
4953
|
if (parts.length >= 1) {
|
|
4961
4954
|
const moduleName = parts[0];
|
|
4962
|
-
const
|
|
4955
|
+
const subfolder = parts[1];
|
|
4963
4956
|
let layer = "root";
|
|
4964
|
-
if (
|
|
4957
|
+
if (subfolder === "domain" || subfolder === "errors") {
|
|
4965
4958
|
layer = "domain";
|
|
4966
|
-
} else if (
|
|
4959
|
+
} else if (subfolder === "ports") {
|
|
4967
4960
|
layer = "ports";
|
|
4968
|
-
} else if (
|
|
4961
|
+
} else if (subfolder === "infrastructure") {
|
|
4969
4962
|
layer = "infrastructure";
|
|
4970
4963
|
}
|
|
4971
4964
|
return { moduleType: "shared", moduleName, feature: moduleName, layer };
|
|
4972
4965
|
}
|
|
4973
4966
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
const parts =
|
|
4967
|
+
if (isUnder(normFeaturesBase, normFullPath)) {
|
|
4968
|
+
const rel = path26__default.default.relative(normFeaturesBase, normFullPath).replace(/\\/g, "/");
|
|
4969
|
+
const parts = rel.split("/").filter(Boolean);
|
|
4977
4970
|
if (parts.length >= 2) {
|
|
4978
4971
|
const moduleName = parts[0];
|
|
4979
4972
|
const subfolder = parts[1];
|
|
@@ -4986,7 +4979,7 @@ function classifyFile(fullPathAbs, featuresBaseAbs, sharedBaseAbs, errorsBaseAbs
|
|
|
4986
4979
|
layer = "services";
|
|
4987
4980
|
} else if (subfolder === "infrastructure") {
|
|
4988
4981
|
layer = "infrastructure";
|
|
4989
|
-
} else if (subfolder
|
|
4982
|
+
} else if (subfolder.startsWith("px") || subfolder === "presentation" || subfolder === "handlers" || subfolder === "transformers") {
|
|
4990
4983
|
layer = "presentation";
|
|
4991
4984
|
}
|
|
4992
4985
|
return { moduleType: "feature", moduleName, feature: moduleName, layer };
|
|
@@ -5015,10 +5008,10 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot, featuresBaseAbs,
|
|
|
5015
5008
|
return { isExternal: true, isDiFramework: false };
|
|
5016
5009
|
}
|
|
5017
5010
|
if (!featuresBaseAbs || !sharedBaseAbs || !errorsBaseAbs) {
|
|
5018
|
-
const
|
|
5019
|
-
featuresBaseAbs = featuresBaseAbs ||
|
|
5020
|
-
sharedBaseAbs = sharedBaseAbs ||
|
|
5021
|
-
errorsBaseAbs = errorsBaseAbs ||
|
|
5011
|
+
const env = resolveLintEnvironment(projectRoot);
|
|
5012
|
+
featuresBaseAbs = featuresBaseAbs || env.featuresBase;
|
|
5013
|
+
sharedBaseAbs = sharedBaseAbs || env.sharedBase;
|
|
5014
|
+
errorsBaseAbs = errorsBaseAbs || env.errorsBase;
|
|
5022
5015
|
}
|
|
5023
5016
|
const isGlobalConfig = specifier === "@configs" || specifier.startsWith("@configs/") || specifier === "@/configs" || specifier.startsWith("@/configs/") || specifier === "~/configs" || specifier.startsWith("~/configs/") || specifier === "configs" || specifier.startsWith("configs/");
|
|
5024
5017
|
let targetAbs = null;
|
|
@@ -5655,45 +5648,108 @@ private ${m.name}(...) { ... }`
|
|
|
5655
5648
|
}
|
|
5656
5649
|
}
|
|
5657
5650
|
}
|
|
5651
|
+
function resolveRegistryModuleError(modDir, modName) {
|
|
5652
|
+
const errorsDir = path26__default.default.join(modDir, "errors");
|
|
5653
|
+
if (!fs17__default.default.existsSync(errorsDir) || !fs17__default.default.statSync(errorsDir).isDirectory()) {
|
|
5654
|
+
return { errorsDir, hasErrorsDir: false };
|
|
5655
|
+
}
|
|
5656
|
+
const entries = fs17__default.default.readdirSync(errorsDir);
|
|
5657
|
+
const errorFiles = entries.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
5658
|
+
const nameNormalized = modName.toLowerCase().replace(/[-_]/g, "");
|
|
5659
|
+
const singularNormalized = toSingular(modName).toLowerCase().replace(/[-_]/g, "");
|
|
5660
|
+
let baseErrorFileName = errorFiles.find((f) => {
|
|
5661
|
+
const baseNoExt = f.replace(/\.(ts|js)$/, "").toLowerCase().replace(/[-_]/g, "");
|
|
5662
|
+
return baseNoExt === `${nameNormalized}error` || baseNoExt === `${singularNormalized}error` || baseNoExt === `${nameNormalized}errors` || baseNoExt === `${singularNormalized}errors`;
|
|
5663
|
+
});
|
|
5664
|
+
if (!baseErrorFileName) {
|
|
5665
|
+
return { errorsDir, hasErrorsDir: true };
|
|
5666
|
+
}
|
|
5667
|
+
const baseErrorFileAbs = path26__default.default.join(errorsDir, baseErrorFileName);
|
|
5668
|
+
const content = fs17__default.default.readFileSync(baseErrorFileAbs, "utf-8");
|
|
5669
|
+
const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
|
|
5670
|
+
let match;
|
|
5671
|
+
const classes = [];
|
|
5672
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
5673
|
+
classes.push({
|
|
5674
|
+
name: match[1],
|
|
5675
|
+
extendsName: match[2],
|
|
5676
|
+
raw: match[0]
|
|
5677
|
+
});
|
|
5678
|
+
}
|
|
5679
|
+
const expectedNames = [
|
|
5680
|
+
`${toPascalCase(modName)}Error`,
|
|
5681
|
+
`${toPascalCase(toSingular(modName))}Error`,
|
|
5682
|
+
`${toPascalCase(modName)}Errors`,
|
|
5683
|
+
`${toPascalCase(toSingular(modName))}Errors`
|
|
5684
|
+
].map((n) => n.toLowerCase());
|
|
5685
|
+
const targetClass = classes.find((c) => expectedNames.includes(c.name.toLowerCase())) || classes.find((c) => c.raw.includes("export")) || classes[0];
|
|
5686
|
+
return {
|
|
5687
|
+
errorsDir,
|
|
5688
|
+
hasErrorsDir: true,
|
|
5689
|
+
baseErrorFileAbs,
|
|
5690
|
+
baseErrorClassName: targetClass ? targetClass.name : void 0,
|
|
5691
|
+
baseErrorExtendsName: targetClass ? targetClass.extendsName : void 0,
|
|
5692
|
+
baseErrorRawClassCode: targetClass ? targetClass.raw : void 0
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5658
5695
|
function lintCodebase(options = {}) {
|
|
5659
5696
|
const startTime = Date.now();
|
|
5660
5697
|
const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
|
|
5661
5698
|
const failFast = !options.complete;
|
|
5662
|
-
const
|
|
5663
|
-
const
|
|
5664
|
-
const
|
|
5665
|
-
if (!
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5699
|
+
const effectiveMode = options.mode || getEffectiveMode(projectRoot, options.registry !== void 0 ? { registry: options.registry } : void 0);
|
|
5700
|
+
const isRegistryMode = effectiveMode === "registry";
|
|
5701
|
+
const { featuresBase, sharedBase, errorsBase, baseErrorAbs } = resolveLintEnvironment(projectRoot);
|
|
5702
|
+
if (!isRegistryMode) {
|
|
5703
|
+
const missingFoundations = [];
|
|
5704
|
+
if (!fs17__default.default.existsSync(featuresBase)) missingFoundations.push("features/");
|
|
5705
|
+
if (!fs17__default.default.existsSync(sharedBase)) missingFoundations.push("shared/");
|
|
5706
|
+
if (!fs17__default.default.existsSync(errorsBase)) missingFoundations.push("errors/");
|
|
5707
|
+
if (missingFoundations.length > 0) {
|
|
5708
|
+
throw new DependencyRuleError(
|
|
5709
|
+
`Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every architecture must contain 'features/', 'shared/', and 'errors/'.`,
|
|
5710
|
+
[],
|
|
5711
|
+
true
|
|
5712
|
+
);
|
|
5713
|
+
}
|
|
5714
|
+
if (!fs17__default.default.existsSync(baseErrorAbs)) {
|
|
5715
|
+
throw new DependencyRuleError(
|
|
5716
|
+
`Project Structure Violation: Base error class not found at '${path26__default.default.relative(projectRoot, baseErrorAbs).replace(/\\/g, "/")}'. Configure paths.baseError in mason.config.json.`,
|
|
5717
|
+
[],
|
|
5718
|
+
true
|
|
5719
|
+
);
|
|
5720
|
+
}
|
|
5721
|
+
} else {
|
|
5722
|
+
if (!fs17__default.default.existsSync(featuresBase) && !fs17__default.default.existsSync(sharedBase)) {
|
|
5723
|
+
throw new DependencyRuleError(
|
|
5724
|
+
`Project Structure Violation: Missing required foundational directories: features/ or shared/. A registry must contain 'features/' or 'shared/'.`,
|
|
5725
|
+
[],
|
|
5726
|
+
true
|
|
5727
|
+
);
|
|
5728
|
+
}
|
|
5674
5729
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
[],
|
|
5681
|
-
true
|
|
5730
|
+
let baseErrorClassName = "AppError";
|
|
5731
|
+
if (!isRegistryMode && fs17__default.default.existsSync(baseErrorAbs)) {
|
|
5732
|
+
const baseErrorContent = fs17__default.default.readFileSync(baseErrorAbs, "utf-8");
|
|
5733
|
+
const baseClassMatch = baseErrorContent.match(
|
|
5734
|
+
/(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
|
|
5682
5735
|
);
|
|
5736
|
+
baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
|
|
5683
5737
|
}
|
|
5684
|
-
const baseErrorContent = fs17__default.default.readFileSync(baseErrorAbs, "utf-8");
|
|
5685
|
-
const baseClassMatch = baseErrorContent.match(
|
|
5686
|
-
/(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
|
|
5687
|
-
);
|
|
5688
|
-
const baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
|
|
5689
5738
|
const features = discoverAllFeatures(featuresBase, [sharedBase, errorsBase]);
|
|
5690
5739
|
const sharedModules = discoverAllShared(sharedBase, [featuresBase, errorsBase]);
|
|
5691
|
-
const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
|
|
5692
5740
|
const allModules = [
|
|
5693
5741
|
...features.map((f) => ({ ...f, type: "feature" })),
|
|
5694
|
-
...sharedModules.map((s) => ({ ...s, type: "shared" }))
|
|
5695
|
-
errorModule
|
|
5742
|
+
...sharedModules.map((s) => ({ ...s, type: "shared" }))
|
|
5696
5743
|
];
|
|
5744
|
+
if (!isRegistryMode) {
|
|
5745
|
+
const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
|
|
5746
|
+
allModules.push(errorModule);
|
|
5747
|
+
} else if (fs17__default.default.existsSync(errorsBase)) {
|
|
5748
|
+
const errorFiles = scanDirFiles(errorsBase, projectRoot);
|
|
5749
|
+
if (errorFiles.length > 0) {
|
|
5750
|
+
allModules.push({ name: "global-errors", dir: errorsBase, type: "error" });
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5697
5753
|
const graph = new FeatureDependencyGraph();
|
|
5698
5754
|
const allViolations = [];
|
|
5699
5755
|
const results = [];
|
|
@@ -5727,6 +5783,77 @@ Code: ${v.code}`;
|
|
|
5727
5783
|
}
|
|
5728
5784
|
for (const mod of allModules) {
|
|
5729
5785
|
const modResult = results.find((r) => r.featureName === mod.name);
|
|
5786
|
+
let modBaseErrorClass = baseErrorClassName;
|
|
5787
|
+
let modBaseErrorAbs = !isRegistryMode ? baseErrorAbs : void 0;
|
|
5788
|
+
if (isRegistryMode && (mod.type === "feature" || mod.type === "shared")) {
|
|
5789
|
+
const errorInfo = resolveRegistryModuleError(mod.dir, mod.name);
|
|
5790
|
+
if (!errorInfo.hasErrorsDir) {
|
|
5791
|
+
addViolation({
|
|
5792
|
+
feature: mod.name,
|
|
5793
|
+
layer: "domain",
|
|
5794
|
+
file: mod.dir,
|
|
5795
|
+
relativePath: path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/"),
|
|
5796
|
+
line: 1,
|
|
5797
|
+
category: "Project Structure Violation",
|
|
5798
|
+
code: `Missing errors/ directory in registry ${mod.type} '${mod.name}'`,
|
|
5799
|
+
ruleId: "REG_ERRORS_DIR_REQUIRED",
|
|
5800
|
+
severity: "error",
|
|
5801
|
+
advice: `Registry ${mod.type} '${mod.name}' must implement its own 'errors/' directory at '${path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}'.`,
|
|
5802
|
+
fixExample: {
|
|
5803
|
+
bad: "// Missing errors directory",
|
|
5804
|
+
good: `mkdir -p ${path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}`
|
|
5805
|
+
}
|
|
5806
|
+
});
|
|
5807
|
+
} else if (!errorInfo.baseErrorFileAbs) {
|
|
5808
|
+
const expectedFileName = `${toPascalCase(mod.name)}Error.ts`;
|
|
5809
|
+
const expectedRel = path26__default.default.relative(projectRoot, path26__default.default.join(errorInfo.errorsDir, expectedFileName)).replace(/\\/g, "/");
|
|
5810
|
+
addViolation({
|
|
5811
|
+
feature: mod.name,
|
|
5812
|
+
layer: "domain",
|
|
5813
|
+
file: errorInfo.errorsDir,
|
|
5814
|
+
relativePath: expectedRel,
|
|
5815
|
+
line: 1,
|
|
5816
|
+
category: "Project Structure Violation",
|
|
5817
|
+
code: `Missing base error file in '${mod.name}/errors'`,
|
|
5818
|
+
ruleId: "REG_BASE_ERROR_REQUIRED",
|
|
5819
|
+
severity: "error",
|
|
5820
|
+
advice: `Registry ${mod.type} '${mod.name}' must have a base error file at '${expectedRel}'.`,
|
|
5821
|
+
fixExample: {
|
|
5822
|
+
bad: "// Missing base error file",
|
|
5823
|
+
good: `export class ${toPascalCase(mod.name)}Error extends Error {
|
|
5824
|
+
constructor(message: string) {
|
|
5825
|
+
super(message);
|
|
5826
|
+
}
|
|
5827
|
+
}`
|
|
5828
|
+
}
|
|
5829
|
+
});
|
|
5830
|
+
} else {
|
|
5831
|
+
modBaseErrorAbs = errorInfo.baseErrorFileAbs;
|
|
5832
|
+
modBaseErrorClass = errorInfo.baseErrorClassName || `${toPascalCase(mod.name)}Error`;
|
|
5833
|
+
if (!errorInfo.baseErrorClassName || errorInfo.baseErrorExtendsName !== "Error") {
|
|
5834
|
+
addViolation({
|
|
5835
|
+
feature: mod.name,
|
|
5836
|
+
layer: "domain",
|
|
5837
|
+
file: errorInfo.baseErrorFileAbs,
|
|
5838
|
+
relativePath: path26__default.default.relative(projectRoot, errorInfo.baseErrorFileAbs).replace(/\\/g, "/"),
|
|
5839
|
+
line: 1,
|
|
5840
|
+
category: "Error Inheritance Violation",
|
|
5841
|
+
code: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
|
|
5842
|
+
ruleId: "REG_BASE_ERROR_MUST_EXTEND_ERROR",
|
|
5843
|
+
severity: "error",
|
|
5844
|
+
advice: `Registry ${mod.type} base error class '${modBaseErrorClass}' in '${mod.name}' must extend native 'Error'.`,
|
|
5845
|
+
fixExample: {
|
|
5846
|
+
bad: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
|
|
5847
|
+
good: `export class ${modBaseErrorClass} extends Error {
|
|
5848
|
+
constructor(message: string) {
|
|
5849
|
+
super(message);
|
|
5850
|
+
}
|
|
5851
|
+
}`
|
|
5852
|
+
}
|
|
5853
|
+
});
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5730
5857
|
const allFiles = scanDirFiles(mod.dir, projectRoot);
|
|
5731
5858
|
modResult.scannedFilesCount = allFiles.length;
|
|
5732
5859
|
for (const file of allFiles) {
|
|
@@ -5754,15 +5881,15 @@ Code: ${v.code}`;
|
|
|
5754
5881
|
code: lineCode,
|
|
5755
5882
|
ruleId: "E3_PLAIN_ERROR_THROW",
|
|
5756
5883
|
severity: "error",
|
|
5757
|
-
advice: `Throwing plain 'Error' is prohibited. You must throw '${
|
|
5884
|
+
advice: isRegistryMode ? `Throwing plain 'Error' is prohibited in registry ${mod.type} '${mod.name}'. You must throw '${modBaseErrorClass}' or a class that inherits from it.` : `Throwing plain 'Error' is prohibited. You must throw '${modBaseErrorClass}' or a custom error class that inherits from it.`,
|
|
5758
5885
|
fixExample: {
|
|
5759
5886
|
bad: lineCode,
|
|
5760
|
-
good: `throw new ${
|
|
5887
|
+
good: `throw new ${modBaseErrorClass}("Something went wrong");`
|
|
5761
5888
|
}
|
|
5762
5889
|
});
|
|
5763
5890
|
}
|
|
5764
5891
|
const isErrorFile = relPath.includes("/errors/") || relPath.startsWith("errors/") || relPath.startsWith("src/errors/") || moduleType === "error" || baseName.endsWith("Error.ts") || baseName.endsWith("Errors.ts") || baseName.endsWith("Error.js") || baseName.endsWith("Errors.js");
|
|
5765
|
-
const isBaseErrorFile = path26__default.default.resolve(file) === path26__default.default.resolve(baseErrorAbs);
|
|
5892
|
+
const isBaseErrorFile = modBaseErrorAbs ? path26__default.default.resolve(file) === path26__default.default.resolve(modBaseErrorAbs) : !isRegistryMode && path26__default.default.resolve(file) === path26__default.default.resolve(baseErrorAbs);
|
|
5766
5893
|
if (isErrorFile && !isBaseErrorFile) {
|
|
5767
5894
|
const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
|
|
5768
5895
|
let classMatch;
|
|
@@ -5784,10 +5911,10 @@ Code: ${v.code}`;
|
|
|
5784
5911
|
code: lineCode,
|
|
5785
5912
|
ruleId: "E4_MISSING_INHERITANCE",
|
|
5786
5913
|
severity: "error",
|
|
5787
|
-
advice: `Error class '${className}' must extend '${
|
|
5914
|
+
advice: isRegistryMode ? `Error class '${className}' in registry ${mod.type} '${mod.name}' must extend '${modBaseErrorClass}'.` : `Error class '${className}' must extend '${modBaseErrorClass}'.`,
|
|
5788
5915
|
fixExample: {
|
|
5789
5916
|
bad: lineCode,
|
|
5790
|
-
good: `export class ${className} extends ${
|
|
5917
|
+
good: `export class ${className} extends ${modBaseErrorClass} { ... }`
|
|
5791
5918
|
}
|
|
5792
5919
|
});
|
|
5793
5920
|
} else if (extendsName === "Error") {
|
|
@@ -5801,10 +5928,10 @@ Code: ${v.code}`;
|
|
|
5801
5928
|
code: lineCode,
|
|
5802
5929
|
ruleId: "E5_NATIVE_ERROR_INHERITANCE",
|
|
5803
5930
|
severity: "error",
|
|
5804
|
-
advice: `Error class '${className}' extends
|
|
5931
|
+
advice: isRegistryMode ? `Error class '${className}' extends native 'Error'. In registry ${mod.type} '${mod.name}', it must extend your base error class ('${modBaseErrorClass}').` : `Error class '${className}' extends native 'Error'. It must extend your configured base error class ('${modBaseErrorClass}').`,
|
|
5805
5932
|
fixExample: {
|
|
5806
5933
|
bad: lineCode,
|
|
5807
|
-
good: `export class ${className} extends ${
|
|
5934
|
+
good: `export class ${className} extends ${modBaseErrorClass} { ... }`
|
|
5808
5935
|
}
|
|
5809
5936
|
});
|
|
5810
5937
|
}
|
|
@@ -6564,7 +6691,9 @@ async function handleLint(flags = {}) {
|
|
|
6564
6691
|
targetDir: flags.targetDir || flags.target,
|
|
6565
6692
|
projectRoot: flags.projectRoot,
|
|
6566
6693
|
fixHint: flags.fixHint !== false,
|
|
6567
|
-
throwOnError: false
|
|
6694
|
+
throwOnError: false,
|
|
6695
|
+
mode: flags.mode || (flags.registry ? "registry" : void 0),
|
|
6696
|
+
registry: Boolean(flags.registry)
|
|
6568
6697
|
};
|
|
6569
6698
|
try {
|
|
6570
6699
|
const summary = lintCodebase(options);
|