deslop-js 0.0.25 → 0.5.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.
- package/README.md +308 -0
- package/dist/index.cjs +24 -18
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +24 -18
- package/dist/parse-worker.mjs +3 -3
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# deslop-js
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.com/package/deslop-js)
|
|
4
|
+
[](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+(
|
|
100
|
-
const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12646
|
+
const emptyDuplicateBlockResult = {
|
|
12644
12647
|
duplicateBlocks: [],
|
|
12645
12648
|
duplicateBlockClusters: [],
|
|
12646
12649
|
shadowedDirectoryPairs: []
|
|
12647
|
-
}
|
|
12648
|
-
const
|
|
12649
|
-
const
|
|
12650
|
-
const
|
|
12651
|
-
const
|
|
12652
|
-
const
|
|
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
|
-
}
|
|
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+(
|
|
68
|
-
const SCRIPT_EXTENSIONLESS_FILE_PATTERN = /(?:^|\s)(?:node|tsx|ts-node|bun|esr|esno|jiti|babel-node|zx)\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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12614
|
+
const emptyDuplicateBlockResult = {
|
|
12612
12615
|
duplicateBlocks: [],
|
|
12613
12616
|
duplicateBlockClusters: [],
|
|
12614
12617
|
shadowedDirectoryPairs: []
|
|
12615
|
-
}
|
|
12616
|
-
const
|
|
12617
|
-
const
|
|
12618
|
-
const
|
|
12619
|
-
const
|
|
12620
|
-
const
|
|
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
|
-
}
|
|
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),
|
package/dist/parse-worker.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.5.6",
|
|
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/
|
|
15
|
+
"homepage": "https://github.com/millionco/react-doctor#readme",
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/millionco/
|
|
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/
|
|
26
|
+
"url": "https://github.com/millionco/react-doctor.git",
|
|
27
27
|
"directory": "packages/deslop-js"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|