deslop-js 0.0.25 → 0.5.8

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/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # deslop-js
2
+
3
+ [![version](https://img.shields.io/npm/v/deslop-js?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/deslop-js)
4
+ [![downloads](https://img.shields.io/npm/dt/deslop-js.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/deslop-js)
5
+
6
+ Deslop JavaScript code.
7
+
8
+ Finds unused files, dead exports, dead dependencies, circular imports, redundant aliases, duplicate types, and other DRY violations. Each finding carries a confidence tier so you can gate CI on the high-signal ones and treat the rest as code-review prompts.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install deslop-js
14
+ ```
15
+
16
+ ## CLI
17
+
18
+ The `deslop-cli` package provides a command-line interface:
19
+
20
+ ```bash
21
+ npm install -g deslop-cli
22
+ ```
23
+
24
+ ### Quick start
25
+
26
+ ```bash
27
+ # scan the current directory
28
+ deslop
29
+
30
+ # scan a specific project
31
+ deslop ./my-project
32
+
33
+ # use the explicit analyze sub-command (equivalent to the above)
34
+ deslop analyze ./my-project
35
+ ```
36
+
37
+ ### Options
38
+
39
+ ```bash
40
+ deslop [root] [options]
41
+
42
+ # custom entry points
43
+ deslop --entry src/main.ts --entry src/worker.ts
44
+
45
+ # ignore test files
46
+ deslop --ignore "**/*.test.ts" --ignore "**/__mocks__/**"
47
+
48
+ # only scan specific extensions
49
+ deslop --extensions .ts .tsx
50
+
51
+ # resolve path aliases via tsconfig
52
+ deslop --tsconfig ./tsconfig.json
53
+
54
+ # add explicit path aliases (in addition to the auto-detected ones)
55
+ deslop --paths "@app/*=src/*" --paths "@lib/*=packages/lib/*"
56
+
57
+ # include type-only exports in results
58
+ deslop --report-types
59
+
60
+ # report unused exports from entry files too
61
+ deslop --include-entry-exports
62
+
63
+ # output results as JSON (useful for CI or piping to other tools)
64
+ deslop --json
65
+
66
+ # exit with code 1 when unused code is found (for CI gates)
67
+ deslop --fail-on-issues
68
+
69
+ # exit with code 1 when circular imports are found
70
+ deslop --fail-on-cycles
71
+ ```
72
+
73
+ ### CI example
74
+
75
+ ```bash
76
+ # fail the build if there are unused exports or circular imports
77
+ deslop ./src --fail-on-issues --fail-on-cycles --ignore "**/*.test.ts"
78
+ ```
79
+
80
+ ## Programmatic Usage
81
+
82
+ ```ts
83
+ import { analyze, defineConfig } from "deslop-js";
84
+
85
+ const config = defineConfig({ rootDir: "./my-project" });
86
+ const result = await analyze(config);
87
+
88
+ // unused-code findings (syntactic)
89
+ result.unusedFiles; // files unreachable from any entry point
90
+ result.unusedExports; // exported symbols never imported
91
+ result.unusedDependencies; // package.json deps not imported anywhere
92
+ result.circularDependencies; // import cycles
93
+
94
+ // redundancy / DRY findings (syntactic, on by default)
95
+ result.redundantAliases; // `import { x as x }`, useless re-export renames
96
+ result.duplicateExports; // same name exported twice from one module
97
+ result.duplicateImports; // same specifier imported multiple times
98
+ result.redundantTypePatterns; // `T & {}`, `Partial<Partial<T>>`, etc.
99
+ result.identityWrappers; // `const wrap = (x) => fn(x)`
100
+ result.duplicateTypeDefinitions; // same-shape type declared in N files
101
+ result.duplicateInlineTypes; // anonymous `{ a, b, c }` repeated across modules
102
+ result.simplifiableFunctions; // `() => { return x }`, `await x; return x`
103
+ result.simplifiableExpressions; // `!!x`, `x ? x : y`, `cond ? true : false`
104
+ result.duplicateConstants; // same literal value across files
105
+ result.crossFileDuplicateExports; // same export name shipped by 2+ files that share an importer
106
+ result.reExportCycles; // `export * from "./a"` cycles (self-loop or multi-node)
107
+ result.privateTypeLeaks; // exported signature references a non-exported local type
108
+
109
+ // duplicate-block detection (token-based copy-paste; on by default, disable via `duplicateBlocks.enabled: false`)
110
+ result.duplicateBlocks; // suffix-array + LCP detected duplicate code blocks
111
+ result.duplicateBlockClusters; // clones grouped by file set + refactoring suggestions
112
+ result.shadowedDirectoryPairs; // directory pairs with many identical files
113
+
114
+ // feature flag inventory (on by default, disable via `featureFlags.enabled: false`)
115
+ result.featureFlags; // LaunchDarkly/Statsig/Unleash/PostHog/Vercel Flags/process.env.* uses
116
+
117
+ // function complexity hotspots (on by default, disable via `complexity.enabled: false`)
118
+ result.complexFunctions; // McCabe cyclomatic + SonarSource cognitive per function
119
+
120
+ // TypeScript-specific smells (on by default)
121
+ result.unnecessaryAssertions; // `x as unknown as T`, `x as any`, `x!!`, `<T>x`, `"foo"!`
122
+ result.lazyImportsAtTopLevel; // top-level `await import(...)` / `.then(...)` that should be static
123
+ result.commonjsInEsm; // `require()`, `module.exports`, `exports.x` inside ESM modules
124
+ result.typeScriptEscapeHatches; // `// @ts-ignore`, `// @ts-nocheck`, undocumented `@ts-expect-error`
125
+
126
+ // semantic findings (type-aware; on by default, disable via `semantic: { enabled: false }`)
127
+ result.unusedTypes; // type aliases / interfaces never referenced
128
+ result.unusedEnumMembers; // enum members no reference site uses
129
+ result.unusedClassMembers; // class members no caller invokes (skips React/Angular lifecycle methods)
130
+ result.misclassifiedDependencies; // `dependencies` entries used only as types
131
+
132
+ // diagnostics
133
+ result.analysisErrors; // structured errors from any pipeline stage
134
+ result.totalFiles;
135
+ result.totalExports;
136
+ result.analysisTimeMs;
137
+ ```
138
+
139
+ ## Programmatic Options
140
+
141
+ `defineConfig` accepts a required `rootDir` and optional overrides:
142
+
143
+ ```ts
144
+ const config = defineConfig({
145
+ rootDir: "./my-project",
146
+ entryPatterns: ["src/main.ts"],
147
+ ignorePatterns: ["**/*.test.ts"],
148
+ tsConfigPath: "./tsconfig.json",
149
+ reportTypes: true,
150
+ includeEntryExports: true,
151
+ reportRedundancy: true,
152
+ semantic: { enabled: true },
153
+ });
154
+ ```
155
+
156
+ | Option | Type | Default | Description |
157
+ | --------------------- | --------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
158
+ | `rootDir` | `string` | required | Project root directory |
159
+ | `entryPatterns` | `string[]` | auto-detected | Entry point glob patterns |
160
+ | `ignorePatterns` | `string[]` | `[]` | Glob patterns to exclude from analysis |
161
+ | `includeExtensions` | `string[]` | `[".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cjs", ".cts"]` | File extensions to scan |
162
+ | `tsConfigPath` | `string \| undefined` | `undefined` | Path to tsconfig.json for path alias resolution |
163
+ | `paths` | `Record<string, string[]> \| undefined` | `undefined` | Explicit path-alias mappings (e.g. `{ "@app/*": ["src/*"] }`), resolved alongside auto-detected aliases |
164
+ | `reportTypes` | `boolean` | `false` | Include type-only exports in `unusedExports` |
165
+ | `includeEntryExports` | `boolean` | `false` | Report unused exports from entry files |
166
+ | `reportRedundancy` | `boolean` | `true` | Emit the redundancy / DRY findings listed above |
167
+ | `semantic` | `SemanticConfig` | `undefined` | Opt-in TypeScript type-aware analysis (see below) |
168
+
169
+ Path aliases are auto-detected by default — from `tsconfig` `paths`, Vite (`resolve.alias`), webpack, Babel (`module-resolver`), and Jest (`moduleNameMapper`) configs, plus the workspace layout (a `@scope/<dir>` import resolves to the matching workspace package even when its `package.json` name differs). Use `paths` / `--paths` only for mappings none of those cover.
170
+
171
+ ### Semantic (type-aware) analysis
172
+
173
+ On by default. Loads the TypeScript program when the project has a valid `tsconfig.json`; gracefully no-ops on JS-only projects. Disable with `semantic: { enabled: false }` to skip the ~1–3s program load.
174
+
175
+ ```ts
176
+ const config = defineConfig({
177
+ rootDir: "./my-project",
178
+ semantic: {
179
+ enabled: true,
180
+ reportUnusedTypes: true,
181
+ reportUnusedEnumMembers: true,
182
+ reportUnusedClassMembers: false, // off by default, noisy on framework code
183
+ reportMisclassifiedDependencies: true,
184
+ reportRedundantVariableAliases: true,
185
+ reportRoundTripAliases: true,
186
+ },
187
+ });
188
+ ```
189
+
190
+ | Option | Default | Notes |
191
+ | --------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
192
+ | `enabled` | `false` | Master switch; semantic analysis loads the TS program and adds ~1–3s per scan |
193
+ | `reportUnusedTypes` | `true` | Type aliases / interfaces / type-only exports never referenced |
194
+ | `reportUnusedEnumMembers` | `true` | Enum members no reference site reads or writes |
195
+ | `reportUnusedClassMembers` | **`false`** | Subclass overrides, framework method-by-name invocation (`@HttpGet`, lifecycle hooks) produce too many stylistic FPs to enable by default. Opt in selectively. |
196
+ | `reportMisclassifiedDependencies` | `true` | `dependencies` packages used only via `import type` |
197
+ | `reportRedundantVariableAliases` | `true` | Local aliases like `const X = Y; export { X }` |
198
+ | `reportRoundTripAliases` | `true` | `import { X as Y } from "./a"; export { Y as X }` |
199
+
200
+ ### Duplicate blocks (token-based copy-paste detection)
201
+
202
+ On by default. Detects maximal duplicated token sequences across files via a suffix array + LCP pass over a normalized AST token stream. Tune thresholds or disable entirely:
203
+
204
+ ```ts
205
+ const config = defineConfig({
206
+ rootDir: "./my-project",
207
+ duplicateBlocks: {
208
+ enabled: true, // default
209
+ mode: "semantic", // "strict" preserves identifiers/literals; "semantic" (default) blinds them
210
+ minTokens: 50,
211
+ minLines: 5,
212
+ minOccurrences: 2,
213
+ skipLocal: false, // true => only report cross-directory duplicates
214
+ },
215
+ });
216
+ ```
217
+
218
+ Surfaces in three result fields:
219
+
220
+ - `result.duplicateBlocks` — every duplicated block group with all its occurrences
221
+ - `result.duplicateBlockClusters` — duplicate blocks sharing the same file set, plus an `extract-function` / `extract-module` refactoring hint
222
+ - `result.shadowedDirectoryPairs` — directory pairs (e.g. `src/` and `deno/lib/`) whose files mirror each other
223
+
224
+ ### Feature flag inventory
225
+
226
+ On by default. Scans the codebase for every place a feature flag is _read_ and produces a finding per use. The detector recognizes three families:
227
+
228
+ 1. **Env-var flags** — `process.env.X` whose name starts with one of the built-in prefixes `FEATURE_`, `NEXT_PUBLIC_FEATURE_`, `REACT_APP_FEATURE_`, `VITE_FEATURE_`, `NUXT_PUBLIC_FEATURE_`, `ENABLE_`, `FF_`, `FLAG_`, `TOGGLE_` (extend with `extraEnvPrefixes`).
229
+ 2. **SDK calls** with provider attribution — LaunchDarkly (`useFlag`/`variation`/...), Statsig (`useGate`/`checkGate`/...), Unleash (`isEnabled`/`getVariant`), GrowthBook (`isOn`/`isOff`/`getFeatureValue`), Split (`getTreatment`), PostHog (`useFeatureFlagEnabled`/...), ConfigCat, Flagsmith, Optimizely, Eppo, and Vercel Flags (`flag()` / `evaluate()` from `flags` or `@vercel/flags`).
230
+ 3. **Config-object access** — `config.features.X` style; off by default because it's heuristic. Opt in with `detectConfigObjects: true`.
231
+
232
+ Each finding carries `name`, `path`, `line`, `column`, `sdkProvider` (when known), and `kind: "env-var" | "sdk-call" | "config-object"`. The detector also tracks the surrounding `if` / ternary guard span and sets `guardsDeadCode: true` when an `unusedExports` finding falls inside that guard — so a flag whose enabled branch contains only dead code lights up immediately.
233
+
234
+ **The actionable angle**: cross-reference `result.featureFlags` with your live flag dashboard.
235
+
236
+ - Flags in the dashboard but missing from `result.featureFlags` → no longer read by the codebase, safe to retire from the platform.
237
+ - Flags in `result.featureFlags` with `guardsDeadCode: true` → the guarded code is unreachable, delete both the flag and its body.
238
+
239
+ ```ts
240
+ const config = defineConfig({
241
+ rootDir: "./my-project",
242
+ featureFlags: {
243
+ enabled: true, // default
244
+ extraEnvPrefixes: ["MYAPP_FF_"],
245
+ extraSdkFunctionNames: ["myCustomFlag"],
246
+ detectConfigObjects: false, // heuristic config.features.x — opt in if you use that pattern
247
+ },
248
+ });
249
+ ```
250
+
251
+ ### Function complexity (cyclomatic + cognitive)
252
+
253
+ On by default. Reports per-function McCabe cyclomatic and SonarSource cognitive complexity, function size, and parameter count for every function that breaches at least one threshold. Tune the thresholds or disable entirely:
254
+
255
+ ```ts
256
+ const config = defineConfig({
257
+ rootDir: "./my-project",
258
+ complexity: {
259
+ enabled: true, // default
260
+ cyclomaticThreshold: 10,
261
+ cognitiveThreshold: 15,
262
+ paramCountThreshold: 5,
263
+ functionLineThreshold: 80,
264
+ },
265
+ });
266
+ ```
267
+
268
+ ### TypeScript code smells
269
+
270
+ On by default. Four families of TypeScript-specific patterns surfaced at high or medium confidence — no extra config required.
271
+
272
+ ```ts
273
+ result.unnecessaryAssertions; // type assertions that drop type-safety or do nothing
274
+ result.lazyImportsAtTopLevel; // dynamic imports at the module top level
275
+ result.commonjsInEsm; // CommonJS forms inside ESM modules
276
+ result.typeScriptEscapeHatches; // @ts-ignore / @ts-nocheck / undocumented @ts-expect-error
277
+ ```
278
+
279
+ | Finding | Kinds |
280
+ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
281
+ | `result.unnecessaryAssertions` | `redundant-double-assertion` (`x as unknown as T`), `assertion-to-any`, `redundant-non-null-on-literal` (`"foo"!`), `double-non-null` (`x!!`), `angle-bracket-assertion` (`<T>x`) |
282
+ | `result.lazyImportsAtTopLevel` | `top-level-await-import`, `top-level-then-import` |
283
+ | `result.commonjsInEsm` | `require`, `module-exports`, `exports-assignment` |
284
+ | `result.typeScriptEscapeHatches` | `ts-ignore`, `ts-nocheck`, `ts-expect-error-without-explanation` |
285
+
286
+ ESM detection follows the runtime rules: `.mts`/`.mjs` extensions are always ESM, `.cts`/`.cjs` are always CommonJS, and other files inherit from the nearest `package.json`'s `"type"` field.
287
+
288
+ ## Findings have confidence tiers
289
+
290
+ Every redundancy / semantic finding carries `confidence: "high" | "medium" | "low"`. Use `"high"` for CI gates; `"medium"` and `"low"` are best treated as code-review prompts since intent is sometimes unknowable from syntax alone (e.g. `?? null` may be required by a typed callback signature).
291
+
292
+ ## Error handling
293
+
294
+ `analyze()` never throws on a corrupted file, unparseable `tsconfig`, or missing dependency. Failures surface as `analysisErrors: DeslopError[]` with structured `code`, `module`, `severity`, and `path` fields. See `errors.ts` for the full taxonomy. Errors at `severity: "info"` (empty files, binary files, minified bundles skipped from redundancy analysis) are informational and do not indicate problems.
295
+
296
+ ## Development
297
+
298
+ ```bash
299
+ pnpm install
300
+ pnpm build
301
+ pnpm test
302
+ pnpm lint
303
+ pnpm format
304
+ ```
305
+
306
+ ## License
307
+
308
+ MIT
package/dist/index.cjs CHANGED
@@ -96,8 +96,8 @@ const DEFAULT_EXCLUSIONS = [
96
96
  "**/*.min.mjs",
97
97
  "**/mockServiceWorker.js"
98
98
  ];
99
- const SCRIPT_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|tsc|npx|bun|esr|esno|jiti|babel-node|zx)\s+(?:(?:-[\w-]+(?:[=\s][\w./@=-]+)?\s+)|(?:[\w/-]+\s+))*([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:\s|$)/;
100
- const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\s+(?:(?:-[\w-]+(?:[=\s][\w./@=-]+)?\s+))*((?:[./]|[\w@][\w@-]*\/)[\w./@-]+)(?:\s|$)/;
99
+ const SCRIPT_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|tsc|npx|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:\s|$)/;
100
+ const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?((?:[./]|[\w@][\w@-]*\/)[\w./@-]+)(?:\s|$)/;
101
101
  const SCRIPT_CONFIG_FILE_PATTERN = /--config\s+([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))/;
102
102
  const SCRIPT_ENTRY_PATTERNS = [];
103
103
  const DEFAULT_ENTRY_GLOBS = [
@@ -1582,7 +1582,7 @@ const extractMdxImportsExports = (sourceText) => {
1582
1582
  return statements.join("\n");
1583
1583
  };
1584
1584
  const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1585
- const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
1585
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script\b[^>]*>/gi;
1586
1586
  const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1587
1587
  const extractAstroSources = (sourceText) => {
1588
1588
  const sections = [];
@@ -1601,7 +1601,7 @@ const extractAstroSources = (sourceText) => {
1601
1601
  }
1602
1602
  return sections.join("\n");
1603
1603
  };
1604
- const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script>/gi;
1604
+ const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script\b[^>]*>/gi;
1605
1605
  const extractVueScriptContent = (sourceText) => {
1606
1606
  const scriptBlocks = [];
1607
1607
  let scriptMatch;
@@ -1609,7 +1609,7 @@ const extractVueScriptContent = (sourceText) => {
1609
1609
  while ((scriptMatch = VUE_SCRIPT_PATTERN.exec(sourceText)) !== null) if (scriptMatch[1]) scriptBlocks.push(scriptMatch[1]);
1610
1610
  return scriptBlocks.join("\n");
1611
1611
  };
1612
- const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script>/gi;
1612
+ const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script\b[^>]*>/gi;
1613
1613
  const extractSvelteScriptContent = (sourceText) => {
1614
1614
  const scriptBlocks = [];
1615
1615
  let scriptMatch;
@@ -3846,7 +3846,7 @@ const matchCompiledMapping = (specifier, mappings) => {
3846
3846
  matchedWildcard = specifier.slice(mapping.prefix.length, specifier.length - mapping.suffix.length);
3847
3847
  } else if (specifier !== mapping.prefix) continue;
3848
3848
  for (const target of mapping.targets) {
3849
- const resolved = resolveAliasTarget(target.replace("*", matchedWildcard));
3849
+ const resolved = resolveAliasTarget(target.replaceAll("*", matchedWildcard));
3850
3850
  if (resolved) return resolved;
3851
3851
  }
3852
3852
  }
@@ -3896,11 +3896,11 @@ const BABEL_CONFIG_GLOBS = [
3896
3896
  ];
3897
3897
  const JEST_CONFIG_GLOBS = ["jest.config.{js,ts,mjs,cjs,json}", "**/jest.config.{js,ts,mjs,cjs,json}"];
3898
3898
  const ALIAS_BLOCK_PATTERN = /alias\s*:\s*\{([\s\S]*?)\}/g;
3899
- const ALIAS_ENTRY_PATTERN = /["']?([@\w$./-]+)["']?\s*:\s*(?:path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["']\s*,?\s*)+)\)|fileURLToPath\(\s*new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)\s*\)|["']([^"']+)["'])/g;
3899
+ const ALIAS_ENTRY_PATTERN = /["']?([@\w$./-]+)["']?\s*:\s*(?:path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["'][\s,]*)+)\)|fileURLToPath\(\s*new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)\s*\)|["']([^"']+)["'])/g;
3900
3900
  const JEST_MODULE_NAME_MAPPER_BLOCK_PATTERN = /moduleNameMapper\s*:\s*\{([\s\S]*?)\}/g;
3901
3901
  const JEST_MODULE_NAME_MAPPER_ENTRY_PATTERN = /["']([^"']+)["']\s*:\s*["']([^"']+)["']/g;
3902
3902
  const WEBPACK_MODULES_BLOCK_PATTERN = /modules\s*:\s*\[([\s\S]*?)\]/g;
3903
- const WEBPACK_PATH_CALL_PATTERN = /path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["']\s*,?\s*)+)\)/g;
3903
+ const WEBPACK_PATH_CALL_PATTERN = /path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["'][\s,]*)+)\)/g;
3904
3904
  const STRING_LITERAL_PATTERN$1 = /["']([^"']+)["']/g;
3905
3905
  const TSCONFIG_FILENAMES = [
3906
3906
  "tsconfig.json",
@@ -5505,7 +5505,7 @@ const extractBundlerConfigEntryPoints = (directory) => {
5505
5505
  };
5506
5506
  const WEBPACK_ENTRY_BLOCK_PATTERN = /entry\s*:\s*(?:\{[^}]*\}|\[[^\]]*\]|['"][^'"]+['"]|path\.(?:join|resolve)\([^)]*\))/gs;
5507
5507
  const WEBPACK_ENTRY_FILE_PATTERN = /['"]([^'"]+)['"]/g;
5508
- const WEBPACK_PATH_JOIN_PATTERN = /path\.(?:join|resolve)\(\s*__dirname\s*,\s*((?:['"][^'"]*['"]\s*,?\s*)+)\)/g;
5508
+ const WEBPACK_PATH_JOIN_PATTERN = /path\.(?:join|resolve)\(\s*__dirname\s*,\s*((?:['"][^'"]*['"][\s,]*)+)\)/g;
5509
5509
  const REQUIRE_RESOLVE_PATTERN = /require\.resolve\(\s*['"]([^'"]+)['"]\s*\)/g;
5510
5510
  const extractWebpackEntryPoints = (directory) => {
5511
5511
  const entries = [];
@@ -7224,10 +7224,13 @@ const discoverToolingEntryPoints = (rootDir, workspacePackages) => {
7224
7224
 
7225
7225
  //#endregion
7226
7226
  //#region src/utils/resolve-available-concurrency.ts
7227
+ const clampParseConcurrency = (value) => Math.max(1, Math.min(Math.floor(value), 16));
7227
7228
  const resolveAvailableConcurrency = () => {
7229
+ const requestedConcurrency = Number(process.env["DESLOP_PARSE_CONCURRENCY"]);
7230
+ if (Number.isFinite(requestedConcurrency) && requestedConcurrency >= 1) return clampParseConcurrency(requestedConcurrency);
7228
7231
  const available = node_os.default.availableParallelism();
7229
7232
  if (!Number.isFinite(available) || available < 1) return 1;
7230
- return Math.max(1, Math.min(Math.floor(available), 16));
7233
+ return clampParseConcurrency(available);
7231
7234
  };
7232
7235
 
7233
7236
  //#endregion
@@ -12640,21 +12643,23 @@ const generateReport = (graph, config) => {
12640
12643
  const simplifiableExpressions = config.reportRedundancy ? safeReportDetector("detectSimplifiableExpressions", () => detectSimplifiableExpressions(graph), [], errorSink) : [];
12641
12644
  const duplicateConstants = config.reportRedundancy ? safeReportDetector("detectDuplicateConstants", () => detectDuplicateConstants(graph), [], errorSink) : [];
12642
12645
  const crossFileDuplicateExports = config.reportRedundancy ? safeReportDetector("detectCrossFileDuplicateExports", () => detectCrossFileDuplicateExports(graph), [], errorSink) : [];
12643
- const duplicateBlockResult = safeReportDetector("detectDuplicateBlocks", () => detectDuplicateBlocks(graph, config.duplicateBlocks, config.rootDir), {
12646
+ const emptyDuplicateBlockResult = {
12644
12647
  duplicateBlocks: [],
12645
12648
  duplicateBlockClusters: [],
12646
12649
  shadowedDirectoryPairs: []
12647
- }, errorSink);
12648
- const reExportCycles = safeReportDetector("detectReExportCycles", () => detectReExportCycles(graph), [], errorSink);
12649
- const featureFlags = safeReportDetector("detectFeatureFlags", () => detectFeatureFlags(graph, config.featureFlags), [], errorSink);
12650
- const complexFunctions = safeReportDetector("detectComplexHotspots", () => detectComplexHotspots(graph, config.complexity), [], errorSink);
12651
- const privateTypeLeaks = safeReportDetector("detectPrivateTypeLeaks", () => detectPrivateTypeLeaks(graph), [], errorSink);
12652
- const typeScriptSmellsResult = safeReportDetector("detectTypeScriptSmells", () => detectTypeScriptSmells(graph), {
12650
+ };
12651
+ const duplicateBlockResult = config.reportCodeQuality ? safeReportDetector("detectDuplicateBlocks", () => detectDuplicateBlocks(graph, config.duplicateBlocks, config.rootDir), emptyDuplicateBlockResult, errorSink) : emptyDuplicateBlockResult;
12652
+ const reExportCycles = config.reportCodeQuality ? safeReportDetector("detectReExportCycles", () => detectReExportCycles(graph), [], errorSink) : [];
12653
+ const featureFlags = config.reportCodeQuality ? safeReportDetector("detectFeatureFlags", () => detectFeatureFlags(graph, config.featureFlags), [], errorSink) : [];
12654
+ const complexFunctions = config.reportCodeQuality ? safeReportDetector("detectComplexHotspots", () => detectComplexHotspots(graph, config.complexity), [], errorSink) : [];
12655
+ const privateTypeLeaks = config.reportCodeQuality ? safeReportDetector("detectPrivateTypeLeaks", () => detectPrivateTypeLeaks(graph), [], errorSink) : [];
12656
+ const emptyTypeScriptSmellsResult = {
12653
12657
  unnecessaryAssertions: [],
12654
12658
  lazyImportsAtTopLevel: [],
12655
12659
  commonjsInEsm: [],
12656
12660
  typeScriptEscapeHatches: []
12657
- }, errorSink);
12661
+ };
12662
+ const typeScriptSmellsResult = config.reportCodeQuality ? safeReportDetector("detectTypeScriptSmells", () => detectTypeScriptSmells(graph), emptyTypeScriptSmellsResult, errorSink) : emptyTypeScriptSmellsResult;
12658
12663
  let semanticResult;
12659
12664
  try {
12660
12665
  semanticResult = runSemanticAnalysis(graph, config);
@@ -12916,6 +12921,7 @@ const defineConfig = (options) => ({
12916
12921
  reportTypes: options.reportTypes ?? false,
12917
12922
  includeEntryExports: options.includeEntryExports ?? false,
12918
12923
  reportRedundancy: options.reportRedundancy ?? true,
12924
+ reportCodeQuality: options.reportCodeQuality ?? true,
12919
12925
  semantic: fillSemanticConfig(options.semantic),
12920
12926
  duplicateBlocks: fillDuplicateBlocksConfig(options.duplicateBlocks),
12921
12927
  featureFlags: fillFeatureFlagsConfig(options.featureFlags),
package/dist/index.d.cts CHANGED
@@ -421,6 +421,16 @@ interface DeslopConfig {
421
421
  reportTypes: boolean;
422
422
  includeEntryExports: boolean;
423
423
  reportRedundancy: boolean;
424
+ /**
425
+ * Run the non-dead-code "code quality" detectors — duplicate-block (copy-paste)
426
+ * detection, complexity hotspots, feature flags, TypeScript smells,
427
+ * private-type leaks, and re-export cycles. On by default. These are by far
428
+ * the most expensive detectors (duplicate-block detection alone can dominate
429
+ * a large-repo scan), and they're independent of the dead-code graph findings
430
+ * (unused files/exports/dependencies, circular dependencies) — so a consumer
431
+ * that only wants dead-code can set this `false` to skip the bulk of the work.
432
+ */
433
+ reportCodeQuality: boolean;
424
434
  semantic: SemanticConfig | undefined;
425
435
  duplicateBlocks: DuplicateBlocksConfig | undefined;
426
436
  featureFlags: FeatureFlagsConfig | undefined;
package/dist/index.d.mts CHANGED
@@ -421,6 +421,16 @@ interface DeslopConfig {
421
421
  reportTypes: boolean;
422
422
  includeEntryExports: boolean;
423
423
  reportRedundancy: boolean;
424
+ /**
425
+ * Run the non-dead-code "code quality" detectors — duplicate-block (copy-paste)
426
+ * detection, complexity hotspots, feature flags, TypeScript smells,
427
+ * private-type leaks, and re-export cycles. On by default. These are by far
428
+ * the most expensive detectors (duplicate-block detection alone can dominate
429
+ * a large-repo scan), and they're independent of the dead-code graph findings
430
+ * (unused files/exports/dependencies, circular dependencies) — so a consumer
431
+ * that only wants dead-code can set this `false` to skip the bulk of the work.
432
+ */
433
+ reportCodeQuality: boolean;
424
434
  semantic: SemanticConfig | undefined;
425
435
  duplicateBlocks: DuplicateBlocksConfig | undefined;
426
436
  featureFlags: FeatureFlagsConfig | undefined;
package/dist/index.mjs CHANGED
@@ -64,8 +64,8 @@ const DEFAULT_EXCLUSIONS = [
64
64
  "**/*.min.mjs",
65
65
  "**/mockServiceWorker.js"
66
66
  ];
67
- const SCRIPT_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|tsc|npx|bun|esr|esno|jiti|babel-node|zx)\s+(?:(?:-[\w-]+(?:[=\s][\w./@=-]+)?\s+)|(?:[\w/-]+\s+))*([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:\s|$)/;
68
- const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\s+(?:(?:-[\w-]+(?:[=\s][\w./@=-]+)?\s+))*((?:[./]|[\w@][\w@-]*\/)[\w./@-]+)(?:\s|$)/;
67
+ const SCRIPT_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|tsc|npx|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))(?:\s|$)/;
68
+ const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\s+(?:\S+\s+)*?((?:[./]|[\w@][\w@-]*\/)[\w./@-]+)(?:\s|$)/;
69
69
  const SCRIPT_CONFIG_FILE_PATTERN = /--config\s+([\w./@-]+\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs))/;
70
70
  const SCRIPT_ENTRY_PATTERNS = [];
71
71
  const DEFAULT_ENTRY_GLOBS = [
@@ -1550,7 +1550,7 @@ const extractMdxImportsExports = (sourceText) => {
1550
1550
  return statements.join("\n");
1551
1551
  };
1552
1552
  const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1553
- const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
1553
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script\b[^>]*>/gi;
1554
1554
  const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1555
1555
  const extractAstroSources = (sourceText) => {
1556
1556
  const sections = [];
@@ -1569,7 +1569,7 @@ const extractAstroSources = (sourceText) => {
1569
1569
  }
1570
1570
  return sections.join("\n");
1571
1571
  };
1572
- const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script>/gi;
1572
+ const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script\b[^>]*>/gi;
1573
1573
  const extractVueScriptContent = (sourceText) => {
1574
1574
  const scriptBlocks = [];
1575
1575
  let scriptMatch;
@@ -1577,7 +1577,7 @@ const extractVueScriptContent = (sourceText) => {
1577
1577
  while ((scriptMatch = VUE_SCRIPT_PATTERN.exec(sourceText)) !== null) if (scriptMatch[1]) scriptBlocks.push(scriptMatch[1]);
1578
1578
  return scriptBlocks.join("\n");
1579
1579
  };
1580
- const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script>/gi;
1580
+ const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script\b[^>]*>/gi;
1581
1581
  const extractSvelteScriptContent = (sourceText) => {
1582
1582
  const scriptBlocks = [];
1583
1583
  let scriptMatch;
@@ -3814,7 +3814,7 @@ const matchCompiledMapping = (specifier, mappings) => {
3814
3814
  matchedWildcard = specifier.slice(mapping.prefix.length, specifier.length - mapping.suffix.length);
3815
3815
  } else if (specifier !== mapping.prefix) continue;
3816
3816
  for (const target of mapping.targets) {
3817
- const resolved = resolveAliasTarget(target.replace("*", matchedWildcard));
3817
+ const resolved = resolveAliasTarget(target.replaceAll("*", matchedWildcard));
3818
3818
  if (resolved) return resolved;
3819
3819
  }
3820
3820
  }
@@ -3864,11 +3864,11 @@ const BABEL_CONFIG_GLOBS = [
3864
3864
  ];
3865
3865
  const JEST_CONFIG_GLOBS = ["jest.config.{js,ts,mjs,cjs,json}", "**/jest.config.{js,ts,mjs,cjs,json}"];
3866
3866
  const ALIAS_BLOCK_PATTERN = /alias\s*:\s*\{([\s\S]*?)\}/g;
3867
- const ALIAS_ENTRY_PATTERN = /["']?([@\w$./-]+)["']?\s*:\s*(?:path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["']\s*,?\s*)+)\)|fileURLToPath\(\s*new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)\s*\)|["']([^"']+)["'])/g;
3867
+ const ALIAS_ENTRY_PATTERN = /["']?([@\w$./-]+)["']?\s*:\s*(?:path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["'][\s,]*)+)\)|fileURLToPath\(\s*new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)\s*\)|["']([^"']+)["'])/g;
3868
3868
  const JEST_MODULE_NAME_MAPPER_BLOCK_PATTERN = /moduleNameMapper\s*:\s*\{([\s\S]*?)\}/g;
3869
3869
  const JEST_MODULE_NAME_MAPPER_ENTRY_PATTERN = /["']([^"']+)["']\s*:\s*["']([^"']+)["']/g;
3870
3870
  const WEBPACK_MODULES_BLOCK_PATTERN = /modules\s*:\s*\[([\s\S]*?)\]/g;
3871
- const WEBPACK_PATH_CALL_PATTERN = /path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["']\s*,?\s*)+)\)/g;
3871
+ const WEBPACK_PATH_CALL_PATTERN = /path\.(?:resolve|join)\(\s*__dirname\s*,\s*((?:["'][^"']+["'][\s,]*)+)\)/g;
3872
3872
  const STRING_LITERAL_PATTERN$1 = /["']([^"']+)["']/g;
3873
3873
  const TSCONFIG_FILENAMES = [
3874
3874
  "tsconfig.json",
@@ -5473,7 +5473,7 @@ const extractBundlerConfigEntryPoints = (directory) => {
5473
5473
  };
5474
5474
  const WEBPACK_ENTRY_BLOCK_PATTERN = /entry\s*:\s*(?:\{[^}]*\}|\[[^\]]*\]|['"][^'"]+['"]|path\.(?:join|resolve)\([^)]*\))/gs;
5475
5475
  const WEBPACK_ENTRY_FILE_PATTERN = /['"]([^'"]+)['"]/g;
5476
- const WEBPACK_PATH_JOIN_PATTERN = /path\.(?:join|resolve)\(\s*__dirname\s*,\s*((?:['"][^'"]*['"]\s*,?\s*)+)\)/g;
5476
+ const WEBPACK_PATH_JOIN_PATTERN = /path\.(?:join|resolve)\(\s*__dirname\s*,\s*((?:['"][^'"]*['"][\s,]*)+)\)/g;
5477
5477
  const REQUIRE_RESOLVE_PATTERN = /require\.resolve\(\s*['"]([^'"]+)['"]\s*\)/g;
5478
5478
  const extractWebpackEntryPoints = (directory) => {
5479
5479
  const entries = [];
@@ -7192,10 +7192,13 @@ const discoverToolingEntryPoints = (rootDir, workspacePackages) => {
7192
7192
 
7193
7193
  //#endregion
7194
7194
  //#region src/utils/resolve-available-concurrency.ts
7195
+ const clampParseConcurrency = (value) => Math.max(1, Math.min(Math.floor(value), 16));
7195
7196
  const resolveAvailableConcurrency = () => {
7197
+ const requestedConcurrency = Number(process.env["DESLOP_PARSE_CONCURRENCY"]);
7198
+ if (Number.isFinite(requestedConcurrency) && requestedConcurrency >= 1) return clampParseConcurrency(requestedConcurrency);
7196
7199
  const available = os.availableParallelism();
7197
7200
  if (!Number.isFinite(available) || available < 1) return 1;
7198
- return Math.max(1, Math.min(Math.floor(available), 16));
7201
+ return clampParseConcurrency(available);
7199
7202
  };
7200
7203
 
7201
7204
  //#endregion
@@ -12608,21 +12611,23 @@ const generateReport = (graph, config) => {
12608
12611
  const simplifiableExpressions = config.reportRedundancy ? safeReportDetector("detectSimplifiableExpressions", () => detectSimplifiableExpressions(graph), [], errorSink) : [];
12609
12612
  const duplicateConstants = config.reportRedundancy ? safeReportDetector("detectDuplicateConstants", () => detectDuplicateConstants(graph), [], errorSink) : [];
12610
12613
  const crossFileDuplicateExports = config.reportRedundancy ? safeReportDetector("detectCrossFileDuplicateExports", () => detectCrossFileDuplicateExports(graph), [], errorSink) : [];
12611
- const duplicateBlockResult = safeReportDetector("detectDuplicateBlocks", () => detectDuplicateBlocks(graph, config.duplicateBlocks, config.rootDir), {
12614
+ const emptyDuplicateBlockResult = {
12612
12615
  duplicateBlocks: [],
12613
12616
  duplicateBlockClusters: [],
12614
12617
  shadowedDirectoryPairs: []
12615
- }, errorSink);
12616
- const reExportCycles = safeReportDetector("detectReExportCycles", () => detectReExportCycles(graph), [], errorSink);
12617
- const featureFlags = safeReportDetector("detectFeatureFlags", () => detectFeatureFlags(graph, config.featureFlags), [], errorSink);
12618
- const complexFunctions = safeReportDetector("detectComplexHotspots", () => detectComplexHotspots(graph, config.complexity), [], errorSink);
12619
- const privateTypeLeaks = safeReportDetector("detectPrivateTypeLeaks", () => detectPrivateTypeLeaks(graph), [], errorSink);
12620
- const typeScriptSmellsResult = safeReportDetector("detectTypeScriptSmells", () => detectTypeScriptSmells(graph), {
12618
+ };
12619
+ const duplicateBlockResult = config.reportCodeQuality ? safeReportDetector("detectDuplicateBlocks", () => detectDuplicateBlocks(graph, config.duplicateBlocks, config.rootDir), emptyDuplicateBlockResult, errorSink) : emptyDuplicateBlockResult;
12620
+ const reExportCycles = config.reportCodeQuality ? safeReportDetector("detectReExportCycles", () => detectReExportCycles(graph), [], errorSink) : [];
12621
+ const featureFlags = config.reportCodeQuality ? safeReportDetector("detectFeatureFlags", () => detectFeatureFlags(graph, config.featureFlags), [], errorSink) : [];
12622
+ const complexFunctions = config.reportCodeQuality ? safeReportDetector("detectComplexHotspots", () => detectComplexHotspots(graph, config.complexity), [], errorSink) : [];
12623
+ const privateTypeLeaks = config.reportCodeQuality ? safeReportDetector("detectPrivateTypeLeaks", () => detectPrivateTypeLeaks(graph), [], errorSink) : [];
12624
+ const emptyTypeScriptSmellsResult = {
12621
12625
  unnecessaryAssertions: [],
12622
12626
  lazyImportsAtTopLevel: [],
12623
12627
  commonjsInEsm: [],
12624
12628
  typeScriptEscapeHatches: []
12625
- }, errorSink);
12629
+ };
12630
+ const typeScriptSmellsResult = config.reportCodeQuality ? safeReportDetector("detectTypeScriptSmells", () => detectTypeScriptSmells(graph), emptyTypeScriptSmellsResult, errorSink) : emptyTypeScriptSmellsResult;
12626
12631
  let semanticResult;
12627
12632
  try {
12628
12633
  semanticResult = runSemanticAnalysis(graph, config);
@@ -12884,6 +12889,7 @@ const defineConfig = (options) => ({
12884
12889
  reportTypes: options.reportTypes ?? false,
12885
12890
  includeEntryExports: options.includeEntryExports ?? false,
12886
12891
  reportRedundancy: options.reportRedundancy ?? true,
12892
+ reportCodeQuality: options.reportCodeQuality ?? true,
12887
12893
  semantic: fillSemanticConfig(options.semantic),
12888
12894
  duplicateBlocks: fillDuplicateBlocksConfig(options.duplicateBlocks),
12889
12895
  featureFlags: fillFeatureFlagsConfig(options.featureFlags),
@@ -1234,7 +1234,7 @@ const extractMdxImportsExports = (sourceText) => {
1234
1234
  return statements.join("\n");
1235
1235
  };
1236
1236
  const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1237
- const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
1237
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script\b[^>]*>/gi;
1238
1238
  const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1239
1239
  const extractAstroSources = (sourceText) => {
1240
1240
  const sections = [];
@@ -1253,7 +1253,7 @@ const extractAstroSources = (sourceText) => {
1253
1253
  }
1254
1254
  return sections.join("\n");
1255
1255
  };
1256
- const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script>/gi;
1256
+ const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script\b[^>]*>/gi;
1257
1257
  const extractVueScriptContent = (sourceText) => {
1258
1258
  const scriptBlocks = [];
1259
1259
  let scriptMatch;
@@ -1261,7 +1261,7 @@ const extractVueScriptContent = (sourceText) => {
1261
1261
  while ((scriptMatch = VUE_SCRIPT_PATTERN.exec(sourceText)) !== null) if (scriptMatch[1]) scriptBlocks.push(scriptMatch[1]);
1262
1262
  return scriptBlocks.join("\n");
1263
1263
  };
1264
- const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script>/gi;
1264
+ const SVELTE_SCRIPT_PATTERN = /<script[^>]*>([\s\S]*?)<\/script\b[^>]*>/gi;
1265
1265
  const extractSvelteScriptContent = (sourceText) => {
1266
1266
  const scriptBlocks = [];
1267
1267
  let scriptMatch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.0.25",
3
+ "version": "0.5.8",
4
4
  "description": "Deslop JavaScript code",
5
5
  "keywords": [
6
6
  "dead-code",
@@ -12,9 +12,9 @@
12
12
  "typescript",
13
13
  "unused"
14
14
  ],
15
- "homepage": "https://github.com/millionco/deslop-js#readme",
15
+ "homepage": "https://github.com/millionco/react-doctor#readme",
16
16
  "bugs": {
17
- "url": "https://github.com/millionco/deslop-js/issues"
17
+ "url": "https://github.com/millionco/react-doctor/issues"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "https://github.com/millionco/deslop-js.git",
26
+ "url": "https://github.com/millionco/react-doctor.git",
27
27
  "directory": "packages/deslop-js"
28
28
  },
29
29
  "files": [