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.
Files changed (2) hide show
  1. package/dist/index.js +93 -18
  2. 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: decl.isTypeOnly()
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Stop Node.js production crashes before merge. 47 detection patterns across 5 categories.",
5
5
  "bin": {
6
6
  "radar": "dist/index.js",