technical-debt-radar 1.7.1 → 1.8.0

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 +123 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12889,6 +12889,7 @@ var require_boundary_checker = __commonJS({
12889
12889
  Object.defineProperty(exports2, "__esModule", { value: true });
12890
12890
  exports2.checkBoundaries = checkBoundaries;
12891
12891
  exports2._resetPathAliasCache = _resetPathAliasCache;
12892
+ exports2._resetModuleParsingCache = _resetModuleParsingCache;
12892
12893
  var shared_1 = require_dist();
12893
12894
  var minimatch_1 = require_commonjs3();
12894
12895
  var ts_morph_1 = require("ts-morph");
@@ -13222,6 +13223,12 @@ var require_boundary_checker = __commonJS({
13222
13223
  continue;
13223
13224
  if (typeOnly)
13224
13225
  continue;
13226
+ if (isNestJS) {
13227
+ const importedNames = getImportedNamesFromSpecifier(sourceFile, specifier);
13228
+ const suppressed = importedNames.length > 0 && importedNames.every((name) => isLegitimateNestJSModuleImport(sourceModule, targetModule, name, input));
13229
+ if (suppressed)
13230
+ continue;
13231
+ }
13225
13232
  violations.push({
13226
13233
  category: "architecture",
13227
13234
  type: "module-boundary-violation",
@@ -13461,6 +13468,117 @@ var require_boundary_checker = __commonJS({
13461
13468
  function _resetPathAliasCache() {
13462
13469
  _cachedAliases = void 0;
13463
13470
  }
13471
+ var _cachedModuleParsing;
13472
+ function parseNestJSModules(input) {
13473
+ if (_cachedModuleParsing)
13474
+ return _cachedModuleParsing;
13475
+ const result = /* @__PURE__ */ new Map();
13476
+ const project = new ts_morph_1.Project({ useInMemoryFileSystem: true });
13477
+ for (const file of input.changedFiles) {
13478
+ if (file.status === "deleted")
13479
+ continue;
13480
+ if (!/\.module\.(ts|js)$/.test(file.path))
13481
+ continue;
13482
+ const sf = project.createSourceFile(file.path, file.content);
13483
+ for (const cls of sf.getClasses()) {
13484
+ const moduleDecorator = cls.getDecorators().find((d) => d.getName() === "Module");
13485
+ if (!moduleDecorator)
13486
+ continue;
13487
+ const className = cls.getName() ?? "";
13488
+ const pathMatch = file.path.match(/src\/([^/]+)\//);
13489
+ const moduleName = pathMatch ? pathMatch[1] : "";
13490
+ if (!moduleName)
13491
+ continue;
13492
+ const callExpr = moduleDecorator.getCallExpression?.();
13493
+ if (!callExpr)
13494
+ continue;
13495
+ const args = callExpr.getArguments();
13496
+ if (args.length === 0 || !ts_morph_1.Node.isObjectLiteralExpression(args[0]))
13497
+ continue;
13498
+ const objLit = args[0];
13499
+ const imports = [];
13500
+ const exports3 = [];
13501
+ const hasForwardRef = /* @__PURE__ */ new Map();
13502
+ const importsProp = objLit.getProperty("imports");
13503
+ if (importsProp && ts_morph_1.Node.isPropertyAssignment(importsProp)) {
13504
+ const init = importsProp.getInitializer();
13505
+ if (init && ts_morph_1.Node.isArrayLiteralExpression(init)) {
13506
+ for (const el of init.getElements()) {
13507
+ if (ts_morph_1.Node.isIdentifier(el)) {
13508
+ imports.push(el.getText());
13509
+ hasForwardRef.set(el.getText(), false);
13510
+ } else if (ts_morph_1.Node.isCallExpression(el)) {
13511
+ const callText = el.getText();
13512
+ const fwdMatch = callText.match(/forwardRef\s*\(\s*\(\)\s*=>\s*(\w+)\s*\)/);
13513
+ if (fwdMatch) {
13514
+ imports.push(fwdMatch[1]);
13515
+ hasForwardRef.set(fwdMatch[1], true);
13516
+ } else {
13517
+ const expr = el.getExpression();
13518
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
13519
+ const obj = expr.getExpression();
13520
+ if (ts_morph_1.Node.isIdentifier(obj)) {
13521
+ imports.push(obj.getText());
13522
+ hasForwardRef.set(obj.getText(), false);
13523
+ }
13524
+ }
13525
+ }
13526
+ }
13527
+ }
13528
+ }
13529
+ }
13530
+ const exportsProp = objLit.getProperty("exports");
13531
+ if (exportsProp && ts_morph_1.Node.isPropertyAssignment(exportsProp)) {
13532
+ const init = exportsProp.getInitializer();
13533
+ if (init && ts_morph_1.Node.isArrayLiteralExpression(init)) {
13534
+ for (const el of init.getElements()) {
13535
+ if (ts_morph_1.Node.isIdentifier(el)) {
13536
+ exports3.push(el.getText());
13537
+ }
13538
+ }
13539
+ }
13540
+ }
13541
+ result.set(moduleName, { moduleName, className, imports, exports: exports3, hasForwardRef });
13542
+ }
13543
+ project.removeSourceFile(sf);
13544
+ }
13545
+ _cachedModuleParsing = result;
13546
+ return result;
13547
+ }
13548
+ function getImportedNamesFromSpecifier(sourceFile, specifier) {
13549
+ const names = [];
13550
+ for (const decl of sourceFile.getImportDeclarations()) {
13551
+ if (decl.getModuleSpecifierValue() !== specifier)
13552
+ continue;
13553
+ const defaultImport = decl.getDefaultImport();
13554
+ if (defaultImport)
13555
+ names.push(defaultImport.getText());
13556
+ for (const named of decl.getNamedImports()) {
13557
+ names.push(named.getName());
13558
+ }
13559
+ }
13560
+ return names;
13561
+ }
13562
+ function isLegitimateNestJSModuleImport(sourceModuleName, targetModuleName, importedClassName, input) {
13563
+ const modules = parseNestJSModules(input);
13564
+ const importingMod = modules.get(sourceModuleName);
13565
+ if (!importingMod)
13566
+ return false;
13567
+ const targetMod = modules.get(targetModuleName);
13568
+ if (!targetMod)
13569
+ return false;
13570
+ const targetClassName = targetMod.className;
13571
+ if (!importingMod.imports.includes(targetClassName))
13572
+ return false;
13573
+ if (importingMod.hasForwardRef.get(targetClassName))
13574
+ return false;
13575
+ if (!targetMod.exports.includes(importedClassName))
13576
+ return false;
13577
+ return true;
13578
+ }
13579
+ function _resetModuleParsingCache() {
13580
+ _cachedModuleParsing = void 0;
13581
+ }
13464
13582
  function normalizeForMatching(filePath) {
13465
13583
  let normalized = filePath.replace(/\\/g, "/");
13466
13584
  if (normalized.startsWith("/")) {
@@ -19209,7 +19327,10 @@ var require_dead_code_detector = __commonJS({
19209
19327
  for (const [otherFilePath, otherText] of allFileTexts) {
19210
19328
  if (otherFilePath === filePath)
19211
19329
  continue;
19212
- if (callPattern.test(otherText)) {
19330
+ if (!callPattern.test(otherText))
19331
+ continue;
19332
+ const importPattern = new RegExp(`import\\s+.*\\b${className}\\b.*from\\s+`);
19333
+ if (importPattern.test(otherText)) {
19213
19334
  hasExternalCall = true;
19214
19335
  break;
19215
19336
  }
@@ -19650,7 +19771,7 @@ var require_orchestrator = __commonJS({
19650
19771
  (0, reliability_detector_1.detectReliabilityIssues)(filteredInput, policy),
19651
19772
  (0, duplication_detector_1.detectDuplication)(filteredInput),
19652
19773
  projectRoot ? (0, missing_tests_detector_1.detectMissingTests)(filteredInput, projectRoot, buildMissingTestsConfig(policy)) : Promise.resolve(null),
19653
- (0, dead_code_detector_1.detectDeadCode)(filteredInput, {}, projectRoot),
19774
+ (0, dead_code_detector_1.detectDeadCode)(filteredInput, { excludeBarrels: false, excludeTypes: false }, projectRoot),
19654
19775
  projectRoot ? Promise.resolve((0, coverage_delta_detector_1.detectCoverageDelta)(projectRoot)) : Promise.resolve(null)
19655
19776
  ]);
19656
19777
  const runtimeViolations = runtimeResult.violations;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
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",