technical-debt-radar 1.0.4 → 1.0.6

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 +130 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16578,6 +16578,31 @@ var require_reliability_detector = __commonJS({
16578
16578
  });
16579
16579
  }
16580
16580
  var LOGGING_PATTERNS = /\b(logger|console\.(?:error|warn)|report|sentry|bugsnag|datadog|newrelic|winston|pino|bunyan)\b/i;
16581
+ var SAFE_FALLBACK_PATTERN = /^return\s+(true|false|null|undefined|void\s+0|0|\[\s*\]|\{\s*\})\s*;?$/;
16582
+ function isSafeFallbackReturn(block) {
16583
+ const statements = block.getStatements();
16584
+ if (statements.length === 0)
16585
+ return false;
16586
+ let returnStatement;
16587
+ let hasOtherSideEffects = false;
16588
+ for (const stmt of statements) {
16589
+ if (ts_morph_1.Node.isReturnStatement(stmt)) {
16590
+ returnStatement = stmt;
16591
+ } else if (ts_morph_1.Node.isVariableStatement(stmt)) {
16592
+ continue;
16593
+ } else {
16594
+ hasOtherSideEffects = true;
16595
+ }
16596
+ }
16597
+ if (!returnStatement || hasOtherSideEffects)
16598
+ return false;
16599
+ const returnText = returnStatement.getText().trim();
16600
+ if (SAFE_FALLBACK_PATTERN.test(returnText))
16601
+ return true;
16602
+ if (statements.length === 1 && returnText.startsWith("return "))
16603
+ return true;
16604
+ return false;
16605
+ }
16581
16606
  function detectMissingErrorLogging(sourceFile, filePath, fns, policy, violations) {
16582
16607
  sourceFile.forEachDescendant((node) => {
16583
16608
  if (!ts_morph_1.Node.isCatchClause(node))
@@ -16596,6 +16621,8 @@ var require_reliability_detector = __commonJS({
16596
16621
  });
16597
16622
  if (hasThrow)
16598
16623
  return;
16624
+ if (isSafeFallbackReturn(block))
16625
+ return;
16599
16626
  const hasIncrement = blockText.includes("++") || blockText.includes("+=");
16600
16627
  const hasPropertyMethodCall = /this\.\w+/.test(blockText);
16601
16628
  const hasAwaitOrDelay = /\bawait\b|\bsetTimeout\b|\bsleep\b|\bdelay\b/i.test(blockText);
@@ -17708,13 +17735,15 @@ var require_dead_code_detector = __commonJS({
17708
17735
  excludeTypes: true,
17709
17736
  severity: "info"
17710
17737
  };
17711
- async function detectDeadCode(input, config = {}) {
17738
+ async function detectDeadCode(input, config = {}, projectRoot) {
17712
17739
  const cfg = { ...DEFAULT_CONFIG, ...config };
17713
17740
  if (!cfg.enabled) {
17714
17741
  return emptyResult();
17715
17742
  }
17716
17743
  const project = createProject(input);
17717
17744
  const sourceFiles = /* @__PURE__ */ new Map();
17745
+ const effectiveRoot = projectRoot ?? input.projectRoot;
17746
+ const { aliases } = parsePathAliases(input, effectiveRoot);
17718
17747
  for (const sf of project.getSourceFiles()) {
17719
17748
  const filePath = normalizeFilePath(sf.getFilePath());
17720
17749
  sourceFiles.set(filePath, sf);
@@ -17730,7 +17759,7 @@ var require_dead_code_detector = __commonJS({
17730
17759
  }
17731
17760
  const importRefs = /* @__PURE__ */ new Map();
17732
17761
  for (const [filePath, sf] of sourceFiles) {
17733
- const refs = extractImportReferences(sf, filePath, project);
17762
+ const refs = extractImportReferences(sf, filePath, project, aliases);
17734
17763
  if (refs.size > 0) {
17735
17764
  importRefs.set(filePath, refs);
17736
17765
  }
@@ -17772,6 +17801,9 @@ var require_dead_code_detector = __commonJS({
17772
17801
  continue;
17773
17802
  if (cfg.excludeTypes && isTypeExport(exp.type))
17774
17803
  continue;
17804
+ const sf = sourceFiles.get(filePath);
17805
+ if (sf && exp.type === "class" && isNestJSDIRegistered(sf, exp.name))
17806
+ continue;
17775
17807
  unusedExports.push({
17776
17808
  export: exp,
17777
17809
  file: filePath,
@@ -17849,7 +17881,7 @@ var require_dead_code_detector = __commonJS({
17849
17881
  }
17850
17882
  return exports3;
17851
17883
  }
17852
- function extractImportReferences(sourceFile, filePath, project) {
17884
+ function extractImportReferences(sourceFile, filePath, project, aliases = []) {
17853
17885
  const refs = /* @__PURE__ */ new Map();
17854
17886
  function addRef(resolvedFile, name) {
17855
17887
  if (!refs.has(resolvedFile)) {
@@ -17859,7 +17891,7 @@ var require_dead_code_detector = __commonJS({
17859
17891
  }
17860
17892
  for (const importDecl of sourceFile.getImportDeclarations()) {
17861
17893
  const specifier = importDecl.getModuleSpecifierValue();
17862
- const resolved = resolveModuleSpecifier(filePath, specifier, project);
17894
+ const resolved = resolveModuleSpecifier(filePath, specifier, project, aliases);
17863
17895
  if (!resolved)
17864
17896
  continue;
17865
17897
  const defaultImport = importDecl.getDefaultImport();
@@ -17879,7 +17911,7 @@ var require_dead_code_detector = __commonJS({
17879
17911
  const specifier = exportDecl.getModuleSpecifierValue();
17880
17912
  if (!specifier)
17881
17913
  continue;
17882
- const resolved = resolveModuleSpecifier(filePath, specifier, project);
17914
+ const resolved = resolveModuleSpecifier(filePath, specifier, project, aliases);
17883
17915
  if (!resolved)
17884
17916
  continue;
17885
17917
  if (exportDecl.isNamespaceExport()) {
@@ -17900,9 +17932,7 @@ var require_dead_code_detector = __commonJS({
17900
17932
  if (args.length === 0 || !ts_morph_1.Node.isStringLiteral(args[0]))
17901
17933
  return;
17902
17934
  const modulePath = args[0].getLiteralValue();
17903
- if (!modulePath.startsWith(".") && !modulePath.startsWith("/"))
17904
- return;
17905
- const resolved = resolveModuleSpecifier(filePath, modulePath, project);
17935
+ const resolved = resolveModuleSpecifier(filePath, modulePath, project, aliases);
17906
17936
  if (!resolved)
17907
17937
  return;
17908
17938
  const parent = node.getParent();
@@ -17955,6 +17985,88 @@ var require_dead_code_detector = __commonJS({
17955
17985
  function isTypeExport(type) {
17956
17986
  return type === "type" || type === "interface" || type === "enum";
17957
17987
  }
17988
+ function parsePathAliases(input, projectRoot) {
17989
+ let tsconfigContent;
17990
+ const tsconfigFile = input.changedFiles.find((f) => f.status !== "deleted" && /tsconfig(?:\.build)?\.json$/.test(f.path));
17991
+ if (tsconfigFile) {
17992
+ tsconfigContent = tsconfigFile.content;
17993
+ }
17994
+ const root = projectRoot ?? input.projectRoot;
17995
+ if (!tsconfigContent && root) {
17996
+ try {
17997
+ const fs9 = require("fs");
17998
+ for (const name of ["tsconfig.json", "tsconfig.build.json"]) {
17999
+ const tsconfigPath = path9.join(root, name);
18000
+ if (fs9.existsSync(tsconfigPath)) {
18001
+ tsconfigContent = fs9.readFileSync(tsconfigPath, "utf-8");
18002
+ break;
18003
+ }
18004
+ }
18005
+ } catch {
18006
+ }
18007
+ }
18008
+ if (!tsconfigContent) {
18009
+ return { aliases: [], baseUrl: "." };
18010
+ }
18011
+ try {
18012
+ const stripped = tsconfigContent.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
18013
+ const tsconfig = JSON.parse(stripped);
18014
+ const compilerOptions = tsconfig.compilerOptions ?? {};
18015
+ const rawBaseUrl = (compilerOptions.baseUrl ?? ".").replace(/\/+$/, "");
18016
+ const baseUrl = rawBaseUrl === "" ? "." : rawBaseUrl;
18017
+ const paths = compilerOptions.paths ?? {};
18018
+ const aliases = [];
18019
+ for (const [pattern, targets] of Object.entries(paths)) {
18020
+ const prefix = pattern.replace(/\/?\*$/, "");
18021
+ const resolvedTargets = targets.map((t) => {
18022
+ const target = t.replace(/\/?\*$/, "");
18023
+ if (baseUrl !== "." && !target.startsWith("/")) {
18024
+ return posixJoinAndNormalize(baseUrl, target);
18025
+ }
18026
+ return target;
18027
+ });
18028
+ aliases.push({ prefix, targets: resolvedTargets });
18029
+ }
18030
+ return { aliases, baseUrl };
18031
+ } catch {
18032
+ return { aliases: [], baseUrl: "." };
18033
+ }
18034
+ }
18035
+ function resolvePathAlias(specifier, aliases, project) {
18036
+ for (const alias of aliases) {
18037
+ if (alias.prefix === "" || specifier === alias.prefix || specifier.startsWith(alias.prefix + "/")) {
18038
+ const remainder = alias.prefix === "" ? specifier : specifier.slice(alias.prefix.length).replace(/^\//, "");
18039
+ for (const target of alias.targets) {
18040
+ const candidate = remainder ? target ? target + "/" + remainder : remainder : target;
18041
+ const resolved = resolveInProject(candidate, project);
18042
+ if (resolved)
18043
+ return resolved;
18044
+ }
18045
+ }
18046
+ }
18047
+ return void 0;
18048
+ }
18049
+ function isNestJSDIRegistered(sourceFile, exportName) {
18050
+ for (const cls of sourceFile.getClasses()) {
18051
+ const className = cls.getName();
18052
+ if (className !== exportName)
18053
+ continue;
18054
+ const decorators = cls.getDecorators();
18055
+ for (const dec of decorators) {
18056
+ const decName = dec.getName();
18057
+ if (decName === "Injectable" || decName === "Controller" || decName === "Guard" || decName === "Resolver" || decName === "Gateway") {
18058
+ return true;
18059
+ }
18060
+ }
18061
+ const extendsClause = cls.getExtends();
18062
+ if (extendsClause) {
18063
+ const extendsText = extendsClause.getText();
18064
+ if (extendsText.includes("PassportStrategy"))
18065
+ return true;
18066
+ }
18067
+ }
18068
+ return false;
18069
+ }
17958
18070
  function createProject(input) {
17959
18071
  const project = new ts_morph_1.Project({
17960
18072
  useInMemoryFileSystem: true,
@@ -17977,13 +18089,16 @@ var require_dead_code_detector = __commonJS({
17977
18089
  const normalized = filePath.replace(/\\/g, "/");
17978
18090
  return normalized.startsWith("/") ? normalized.slice(1) : normalized;
17979
18091
  }
17980
- function resolveModuleSpecifier(sourcePath, specifier, project) {
17981
- if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
17982
- return void 0;
18092
+ function resolveModuleSpecifier(sourcePath, specifier, project, aliases = []) {
18093
+ if (specifier.startsWith(".") || specifier.startsWith("/")) {
18094
+ const sourceDir = posixDirname(sourcePath);
18095
+ const resolved = posixJoinAndNormalize(sourceDir, specifier);
18096
+ return resolveInProject(resolved, project) ?? resolved;
17983
18097
  }
17984
- const sourceDir = posixDirname(sourcePath);
17985
- const resolved = posixJoinAndNormalize(sourceDir, specifier);
17986
- return resolveInProject(resolved, project) ?? resolved;
18098
+ if (aliases.length > 0) {
18099
+ return resolvePathAlias(specifier, aliases, project);
18100
+ }
18101
+ return void 0;
17987
18102
  }
17988
18103
  function resolveInProject(resolved, project) {
17989
18104
  const lookup = "/" + resolved;
@@ -18429,7 +18544,7 @@ var require_orchestrator = __commonJS({
18429
18544
  (0, reliability_detector_1.detectReliabilityIssues)(filteredInput, policy),
18430
18545
  (0, duplication_detector_1.detectDuplication)(filteredInput),
18431
18546
  projectRoot ? (0, missing_tests_detector_1.detectMissingTests)(filteredInput, projectRoot) : Promise.resolve(null),
18432
- (0, dead_code_detector_1.detectDeadCode)(filteredInput),
18547
+ (0, dead_code_detector_1.detectDeadCode)(filteredInput, {}, projectRoot),
18433
18548
  projectRoot ? Promise.resolve((0, coverage_delta_detector_1.detectCoverageDelta)(projectRoot)) : Promise.resolve(null)
18434
18549
  ]);
18435
18550
  const runtimeViolations = runtimeResult.violations;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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",