technical-debt-radar 1.0.3 → 1.0.4
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/index.js +93 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12957,6 +12957,95 @@ var require_boundary_checker = __commonJS({
|
|
|
12957
12957
|
}
|
|
12958
12958
|
return violations;
|
|
12959
12959
|
}
|
|
12960
|
+
function isInConstructorShorthandParam(node) {
|
|
12961
|
+
let current = node;
|
|
12962
|
+
while (current) {
|
|
12963
|
+
if (current.getKind() === ts_morph_1.SyntaxKind.Parameter) {
|
|
12964
|
+
const paramText = current.getText();
|
|
12965
|
+
if (/^\s*(private|protected|public|readonly)\s/.test(paramText)) {
|
|
12966
|
+
const paramParent = current.getParent();
|
|
12967
|
+
if (paramParent && paramParent.getKind() === ts_morph_1.SyntaxKind.Constructor) {
|
|
12968
|
+
return true;
|
|
12969
|
+
}
|
|
12970
|
+
}
|
|
12971
|
+
return false;
|
|
12972
|
+
}
|
|
12973
|
+
current = current.getParent();
|
|
12974
|
+
}
|
|
12975
|
+
return false;
|
|
12976
|
+
}
|
|
12977
|
+
function isTypePosition(node) {
|
|
12978
|
+
if (isInConstructorShorthandParam(node))
|
|
12979
|
+
return false;
|
|
12980
|
+
const parent = node.getParent();
|
|
12981
|
+
if (!parent)
|
|
12982
|
+
return false;
|
|
12983
|
+
const parentKind = parent.getKind();
|
|
12984
|
+
if (parentKind === ts_morph_1.SyntaxKind.TypeReference || parentKind === ts_morph_1.SyntaxKind.TypeQuery || parentKind === ts_morph_1.SyntaxKind.TypeAliasDeclaration || parentKind === ts_morph_1.SyntaxKind.InterfaceDeclaration || parentKind === ts_morph_1.SyntaxKind.AsExpression || parentKind === ts_morph_1.SyntaxKind.TypeAssertionExpression || parentKind === ts_morph_1.SyntaxKind.ExpressionWithTypeArguments || parentKind === ts_morph_1.SyntaxKind.MappedType || parentKind === ts_morph_1.SyntaxKind.ConditionalType || parentKind === ts_morph_1.SyntaxKind.IntersectionType || parentKind === ts_morph_1.SyntaxKind.UnionType || parentKind === ts_morph_1.SyntaxKind.TupleType || parentKind === ts_morph_1.SyntaxKind.ArrayType || parentKind === ts_morph_1.SyntaxKind.IndexedAccessType || parentKind === ts_morph_1.SyntaxKind.TypeOperator || parentKind === ts_morph_1.SyntaxKind.ParenthesizedType) {
|
|
12985
|
+
return true;
|
|
12986
|
+
}
|
|
12987
|
+
if (parentKind === ts_morph_1.SyntaxKind.Parameter || parentKind === ts_morph_1.SyntaxKind.PropertyDeclaration || parentKind === ts_morph_1.SyntaxKind.PropertySignature || parentKind === ts_morph_1.SyntaxKind.VariableDeclaration || parentKind === ts_morph_1.SyntaxKind.FunctionDeclaration || parentKind === ts_morph_1.SyntaxKind.MethodDeclaration || parentKind === ts_morph_1.SyntaxKind.MethodSignature || parentKind === ts_morph_1.SyntaxKind.ArrowFunction || parentKind === ts_morph_1.SyntaxKind.GetAccessor || parentKind === ts_morph_1.SyntaxKind.SetAccessor) {
|
|
12988
|
+
const colonToken = parent.getFirstChildByKind(ts_morph_1.SyntaxKind.ColonToken);
|
|
12989
|
+
if (colonToken && node.getPos() > colonToken.getPos()) {
|
|
12990
|
+
const equalsToken = parent.getFirstChildByKind(ts_morph_1.SyntaxKind.EqualsToken);
|
|
12991
|
+
if (!equalsToken || node.getPos() < equalsToken.getPos()) {
|
|
12992
|
+
return true;
|
|
12993
|
+
}
|
|
12994
|
+
}
|
|
12995
|
+
}
|
|
12996
|
+
if (parentKind === ts_morph_1.SyntaxKind.HeritageClause) {
|
|
12997
|
+
return true;
|
|
12998
|
+
}
|
|
12999
|
+
if (parentKind === ts_morph_1.SyntaxKind.QualifiedName) {
|
|
13000
|
+
const grandParent = parent.getParent();
|
|
13001
|
+
if (grandParent && grandParent.getKind() === ts_morph_1.SyntaxKind.TypeReference) {
|
|
13002
|
+
return true;
|
|
13003
|
+
}
|
|
13004
|
+
}
|
|
13005
|
+
return false;
|
|
13006
|
+
}
|
|
13007
|
+
function isImplicitlyTypeOnly(decl, sourceFile) {
|
|
13008
|
+
if (decl.isTypeOnly())
|
|
13009
|
+
return false;
|
|
13010
|
+
if (decl.getNamespaceImport())
|
|
13011
|
+
return false;
|
|
13012
|
+
const defaultImport = decl.getDefaultImport();
|
|
13013
|
+
const namedImports = decl.getNamedImports();
|
|
13014
|
+
if (!defaultImport && namedImports.length === 0)
|
|
13015
|
+
return false;
|
|
13016
|
+
const importedNames = [];
|
|
13017
|
+
if (defaultImport) {
|
|
13018
|
+
importedNames.push(defaultImport.getText());
|
|
13019
|
+
}
|
|
13020
|
+
for (const named of namedImports) {
|
|
13021
|
+
if (named.isTypeOnly())
|
|
13022
|
+
continue;
|
|
13023
|
+
importedNames.push(named.getName());
|
|
13024
|
+
}
|
|
13025
|
+
if (importedNames.length === 0)
|
|
13026
|
+
return true;
|
|
13027
|
+
for (const name of importedNames) {
|
|
13028
|
+
const identifiers = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.Identifier).filter((id) => id.getText() === name);
|
|
13029
|
+
const usages = identifiers.filter((id) => {
|
|
13030
|
+
const parent = id.getParent();
|
|
13031
|
+
if (!parent)
|
|
13032
|
+
return false;
|
|
13033
|
+
const parentKind = parent.getKind();
|
|
13034
|
+
if (parentKind === ts_morph_1.SyntaxKind.ImportSpecifier || parentKind === ts_morph_1.SyntaxKind.ImportClause) {
|
|
13035
|
+
return false;
|
|
13036
|
+
}
|
|
13037
|
+
return true;
|
|
13038
|
+
});
|
|
13039
|
+
if (usages.length === 0)
|
|
13040
|
+
return false;
|
|
13041
|
+
for (const usage of usages) {
|
|
13042
|
+
if (!isTypePosition(usage)) {
|
|
13043
|
+
return false;
|
|
13044
|
+
}
|
|
13045
|
+
}
|
|
13046
|
+
}
|
|
13047
|
+
return true;
|
|
13048
|
+
}
|
|
12960
13049
|
function checkCrossModuleDirectImports(input, policy, activeExceptions) {
|
|
12961
13050
|
const violations = [];
|
|
12962
13051
|
const hasCrossModuleDeny = policy.rules.some((r) => r.type === "deny" && /cross-module/i.test(r.description ?? "") || /cross-module/i.test(r.source));
|
|
@@ -12979,10 +13068,12 @@ var require_boundary_checker = __commonJS({
|
|
|
12979
13068
|
}
|
|
12980
13069
|
const specifiers = [];
|
|
12981
13070
|
for (const decl of sourceFile.getImportDeclarations()) {
|
|
13071
|
+
const explicitTypeOnly = decl.isTypeOnly();
|
|
13072
|
+
const implicitTypeOnly = !explicitTypeOnly && isImplicitlyTypeOnly(decl, sourceFile);
|
|
12982
13073
|
specifiers.push({
|
|
12983
13074
|
specifier: decl.getModuleSpecifierValue(),
|
|
12984
13075
|
line: decl.getStartLineNumber(),
|
|
12985
|
-
typeOnly:
|
|
13076
|
+
typeOnly: explicitTypeOnly || implicitTypeOnly
|
|
12986
13077
|
});
|
|
12987
13078
|
}
|
|
12988
13079
|
for (const reqSpec of extractRequireSpecifiers(sourceFile)) {
|
|
@@ -13012,24 +13103,8 @@ var require_boundary_checker = __commonJS({
|
|
|
13012
13103
|
continue;
|
|
13013
13104
|
if (!isNestJS && isFeatureModule && isNestJSExemptImport(filePath, resolvedTarget, policy.sharedInfrastructure))
|
|
13014
13105
|
continue;
|
|
13015
|
-
if (typeOnly)
|
|
13016
|
-
violations.push({
|
|
13017
|
-
category: "architecture",
|
|
13018
|
-
type: "module-boundary-violation",
|
|
13019
|
-
ruleId: shared_1.ARCHITECTURE_RULES.MODULE_BOUNDARY,
|
|
13020
|
-
severity: "warning",
|
|
13021
|
-
source: "deterministic",
|
|
13022
|
-
confidence: "high",
|
|
13023
|
-
file: filePath,
|
|
13024
|
-
line,
|
|
13025
|
-
module: sourceModule,
|
|
13026
|
-
message: `Cross-module type import: "${sourceModule}" \u2192 "${targetModule}" \u2014 type-only import from '${specifier}' (${filePath})`,
|
|
13027
|
-
explanation: `Type-only import across modules does not create a runtime dependency, but indicates coupling. Consider moving the type to a shared contract.`,
|
|
13028
|
-
debtPoints: 1,
|
|
13029
|
-
gateAction: "warn"
|
|
13030
|
-
});
|
|
13106
|
+
if (typeOnly)
|
|
13031
13107
|
continue;
|
|
13032
|
-
}
|
|
13033
13108
|
violations.push({
|
|
13034
13109
|
category: "architecture",
|
|
13035
13110
|
type: "module-boundary-violation",
|