pi-lens 2.2.9 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similarity Runner: Detect semantic code reuse opportunities
|
|
3
|
+
*
|
|
4
|
+
* Uses Amain's 57×72 state matrix algorithm to find similar functions.
|
|
5
|
+
* Integrated into dispatch flow as a warning (non-blocking) suggestion.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
import { EXCLUDED_DIRS } from "../../file-utils.js";
|
|
12
|
+
import {
|
|
13
|
+
buildProjectIndex,
|
|
14
|
+
findSimilarFunctions,
|
|
15
|
+
loadIndex,
|
|
16
|
+
type ProjectIndex,
|
|
17
|
+
} from "../../project-index.js";
|
|
18
|
+
import { buildStateMatrix, countTransitions } from "../../state-matrix.js";
|
|
19
|
+
import type {
|
|
20
|
+
Diagnostic,
|
|
21
|
+
DispatchContext,
|
|
22
|
+
RunnerDefinition,
|
|
23
|
+
RunnerResult,
|
|
24
|
+
} from "../types.js";
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Configuration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
const CONFIG = {
|
|
31
|
+
SIMILARITY_THRESHOLD: 0.75, // 75% minimum similarity
|
|
32
|
+
MIN_TRANSITIONS: 20, // Skip functions with <20 AST transitions
|
|
33
|
+
MAX_SUGGESTIONS: 3, // Max 3 suggestions per file
|
|
34
|
+
USAGE_THRESHOLD: 2, // Only suggest utilities with 2+ uses (placeholder)
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Runner Implementation
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
const similarityRunner: RunnerDefinition = {
|
|
42
|
+
id: "similarity",
|
|
43
|
+
appliesTo: ["jsts"], // TypeScript/JavaScript only for MVP
|
|
44
|
+
priority: 35, // After ts-lsp, before ast-grep
|
|
45
|
+
enabledByDefault: true,
|
|
46
|
+
|
|
47
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
48
|
+
const { filePath } = ctx;
|
|
49
|
+
|
|
50
|
+
// Only check TypeScript files
|
|
51
|
+
if (!filePath.match(/\.tsx?$/)) {
|
|
52
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Load file content
|
|
56
|
+
const content = await fs.readFile(filePath, "utf-8").catch(() => null);
|
|
57
|
+
if (!content) {
|
|
58
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Find project root and load index
|
|
62
|
+
const projectRoot = await findProjectRoot(filePath);
|
|
63
|
+
if (!projectRoot) {
|
|
64
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const index = await loadOrBuildIndex(projectRoot);
|
|
68
|
+
if (!index || index.entries.size === 0) {
|
|
69
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse the file
|
|
73
|
+
const sourceFile = ts.createSourceFile(
|
|
74
|
+
filePath,
|
|
75
|
+
content,
|
|
76
|
+
ts.ScriptTarget.Latest,
|
|
77
|
+
true,
|
|
78
|
+
ts.ScriptKind.TS,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Extract functions and check for similarities
|
|
82
|
+
const newFunctions = extractFunctions(sourceFile, content);
|
|
83
|
+
|
|
84
|
+
const diagnostics: Diagnostic[] = [];
|
|
85
|
+
|
|
86
|
+
for (const func of newFunctions) {
|
|
87
|
+
// Guardrail: Skip tiny functions
|
|
88
|
+
if (func.transitionCount < CONFIG.MIN_TRANSITIONS) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Find similar functions in index
|
|
93
|
+
const matches = findSimilarFunctions(
|
|
94
|
+
func.matrix,
|
|
95
|
+
index,
|
|
96
|
+
CONFIG.SIMILARITY_THRESHOLD,
|
|
97
|
+
CONFIG.MAX_SUGGESTIONS,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Create diagnostic for each match
|
|
101
|
+
for (const match of matches) {
|
|
102
|
+
// Skip if it's the same function (self-match by path/name)
|
|
103
|
+
if (
|
|
104
|
+
match.targetId ===
|
|
105
|
+
`${path.relative(projectRoot, filePath)}:${func.name}`
|
|
106
|
+
) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
diagnostics.push({
|
|
111
|
+
id: `similarity-${func.name}-${match.targetId}`,
|
|
112
|
+
tool: "similarity",
|
|
113
|
+
filePath,
|
|
114
|
+
line: func.line,
|
|
115
|
+
column: func.column,
|
|
116
|
+
message: buildSuggestionMessage(func, match),
|
|
117
|
+
severity: "warning", // 🟡 Not blocking
|
|
118
|
+
semantic: "warning",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Return limited number of suggestions
|
|
124
|
+
const limitedResults = diagnostics.slice(0, CONFIG.MAX_SUGGESTIONS);
|
|
125
|
+
|
|
126
|
+
if (limitedResults.length === 0) {
|
|
127
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
status: "succeeded",
|
|
132
|
+
diagnostics: limitedResults,
|
|
133
|
+
semantic: "warning",
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Function Extraction
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
142
|
+
interface ExtractedFunction {
|
|
143
|
+
name: string;
|
|
144
|
+
line: number;
|
|
145
|
+
column: number;
|
|
146
|
+
matrix: number[][];
|
|
147
|
+
transitionCount: number;
|
|
148
|
+
signature: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractFunctions(
|
|
152
|
+
sourceFile: ts.SourceFile,
|
|
153
|
+
_fullContent: string,
|
|
154
|
+
): ExtractedFunction[] {
|
|
155
|
+
const functions: ExtractedFunction[] = [];
|
|
156
|
+
|
|
157
|
+
function visit(node: ts.Node) {
|
|
158
|
+
// Function declarations
|
|
159
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
160
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
161
|
+
node.getStart(sourceFile),
|
|
162
|
+
);
|
|
163
|
+
const funcCode = getNodeText(node, sourceFile);
|
|
164
|
+
const matrix = buildStateMatrix(funcCode);
|
|
165
|
+
const transitionCount = countTransitions(matrix);
|
|
166
|
+
|
|
167
|
+
functions.push({
|
|
168
|
+
name: node.name.text,
|
|
169
|
+
line: line + 1, // 1-indexed
|
|
170
|
+
column: character + 1, // 1-indexed
|
|
171
|
+
matrix,
|
|
172
|
+
transitionCount,
|
|
173
|
+
signature: getSignature(node),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Arrow functions assigned to const
|
|
178
|
+
if (ts.isVariableStatement(node)) {
|
|
179
|
+
extractArrowFunctions(node, functions, sourceFile);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
ts.forEachChild(node, visit);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
visit(sourceFile);
|
|
186
|
+
return functions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function extractArrowFunctions(
|
|
190
|
+
node: ts.VariableStatement,
|
|
191
|
+
functions: ExtractedFunction[],
|
|
192
|
+
sourceFile: ts.SourceFile,
|
|
193
|
+
): void {
|
|
194
|
+
for (const decl of node.declarationList.declarations) {
|
|
195
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const func = decl.initializer;
|
|
200
|
+
if (!ts.isArrowFunction(func) && !ts.isFunctionExpression(func)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
205
|
+
node.getStart(sourceFile),
|
|
206
|
+
);
|
|
207
|
+
const funcCode = getNodeText(func, sourceFile);
|
|
208
|
+
const matrix = buildStateMatrix(funcCode);
|
|
209
|
+
const transitionCount = countTransitions(matrix);
|
|
210
|
+
|
|
211
|
+
functions.push({
|
|
212
|
+
name: decl.name.text,
|
|
213
|
+
line: line + 1,
|
|
214
|
+
column: character + 1,
|
|
215
|
+
matrix,
|
|
216
|
+
transitionCount,
|
|
217
|
+
signature: getArrowSignature(func),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getNodeText(node: ts.Node, sourceFile: ts.SourceFile): string {
|
|
223
|
+
return sourceFile.text.substring(node.getStart(sourceFile), node.getEnd());
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function getSignature(node: ts.FunctionDeclaration): string {
|
|
227
|
+
const params = node.parameters
|
|
228
|
+
.map((p) => (ts.isIdentifier(p.name) ? p.name.text : "param"))
|
|
229
|
+
.join(", ");
|
|
230
|
+
return `(${params})`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function getArrowSignature(
|
|
234
|
+
node: ts.ArrowFunction | ts.FunctionExpression,
|
|
235
|
+
): string {
|
|
236
|
+
const params = node.parameters
|
|
237
|
+
.map((p) => (ts.isIdentifier(p.name) ? p.name.text : "param"))
|
|
238
|
+
.join(", ");
|
|
239
|
+
return `(${params})`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Message Building
|
|
244
|
+
// ============================================================================
|
|
245
|
+
|
|
246
|
+
function buildSuggestionMessage(
|
|
247
|
+
func: ExtractedFunction,
|
|
248
|
+
match: {
|
|
249
|
+
targetId: string;
|
|
250
|
+
targetName: string;
|
|
251
|
+
targetLocation: string;
|
|
252
|
+
similarity: number;
|
|
253
|
+
},
|
|
254
|
+
): string {
|
|
255
|
+
const similarityPct = Math.round(match.similarity * 100);
|
|
256
|
+
const parts = match.targetId.split(":");
|
|
257
|
+
const file = parts[0];
|
|
258
|
+
const name = parts[1] || match.targetName;
|
|
259
|
+
const location = `${file}:1`; // TODO: get actual line
|
|
260
|
+
|
|
261
|
+
return `Function '${func.name}' has ${similarityPct}% similarity to existing utility '${name}()' in ${location}. Consider reusing the existing utility.`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// Index Management
|
|
266
|
+
// ============================================================================
|
|
267
|
+
|
|
268
|
+
const indexCache = new Map<string, ProjectIndex>();
|
|
269
|
+
|
|
270
|
+
async function findProjectRoot(filePath: string): Promise<string | null> {
|
|
271
|
+
let dir = path.dirname(filePath);
|
|
272
|
+
while (dir !== path.dirname(dir)) {
|
|
273
|
+
try {
|
|
274
|
+
await fs.access(path.join(dir, "package.json"));
|
|
275
|
+
return dir;
|
|
276
|
+
} catch {
|
|
277
|
+
dir = path.dirname(dir);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function loadOrBuildIndex(
|
|
284
|
+
projectRoot: string,
|
|
285
|
+
): Promise<ProjectIndex | null> {
|
|
286
|
+
// Check cache
|
|
287
|
+
const cached = indexCache.get(projectRoot);
|
|
288
|
+
if (cached) {
|
|
289
|
+
return cached;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Try to load existing index
|
|
293
|
+
const existing = await loadIndex(projectRoot);
|
|
294
|
+
if (existing) {
|
|
295
|
+
indexCache.set(projectRoot, existing);
|
|
296
|
+
return existing;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Build new index
|
|
300
|
+
const { glob } = await import("glob");
|
|
301
|
+
// Build ignore patterns from centralized EXCLUDED_DIRS
|
|
302
|
+
const ignorePatterns = [
|
|
303
|
+
...EXCLUDED_DIRS.map((d) => `**/${d}/**`),
|
|
304
|
+
"**/*.test.ts",
|
|
305
|
+
"**/*.spec.ts",
|
|
306
|
+
"**/*.poc.test.ts",
|
|
307
|
+
];
|
|
308
|
+
const files = await glob("**/*.ts", {
|
|
309
|
+
cwd: projectRoot,
|
|
310
|
+
ignore: ignorePatterns,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (files.length === 0) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const absoluteFiles = files.map((f) => path.join(projectRoot, f));
|
|
318
|
+
const index = await buildProjectIndex(projectRoot, absoluteFiles);
|
|
319
|
+
|
|
320
|
+
indexCache.set(projectRoot, index);
|
|
321
|
+
return index;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// Testing Helper
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
export async function buildIndexForTesting(
|
|
329
|
+
projectRoot: string,
|
|
330
|
+
): Promise<ProjectIndex> {
|
|
331
|
+
const index = await loadOrBuildIndex(projectRoot);
|
|
332
|
+
if (!index) {
|
|
333
|
+
throw new Error("Failed to build index");
|
|
334
|
+
}
|
|
335
|
+
return index;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export { CONFIG };
|
|
339
|
+
export default similarityRunner;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spellcheck runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Uses typos-cli (Rust-based, fast, zero-config) to check spelling in:
|
|
5
|
+
* - Markdown files (.md, .mdx)
|
|
6
|
+
* - Code comments (optional, if typos is configured)
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Fast (Rust-based, ~10x faster than cspell)
|
|
10
|
+
* - Low false positives (only checks known typos)
|
|
11
|
+
* - Zero-config by default
|
|
12
|
+
* - JSON output for easy parsing
|
|
13
|
+
*
|
|
14
|
+
* Alternative considered: cspell
|
|
15
|
+
* - cspell: More comprehensive, but higher false positives, needs config
|
|
16
|
+
* - typos-cli: Faster, less noise, works out of the box
|
|
17
|
+
*
|
|
18
|
+
* Install: cargo install typos-cli
|
|
19
|
+
* Or: npm install -g typos-cli (if wrapped)
|
|
20
|
+
*/
|
|
21
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
22
|
+
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
23
|
+
const typos = createAvailabilityChecker("typos", ".exe");
|
|
24
|
+
/**
|
|
25
|
+
* Parse typos-cli JSON output (JSON Lines format)
|
|
26
|
+
*
|
|
27
|
+
* Each line is a JSON object:
|
|
28
|
+
* {
|
|
29
|
+
* "path": "file.md",
|
|
30
|
+
* "line_num": 42,
|
|
31
|
+
* "byte_offset": 1234,
|
|
32
|
+
* "typo": "recieve",
|
|
33
|
+
* "corrections": ["receive"]
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
function parseTyposOutput(raw, filePath) {
|
|
37
|
+
const diagnostics = [];
|
|
38
|
+
if (!raw.trim()) {
|
|
39
|
+
return diagnostics;
|
|
40
|
+
}
|
|
41
|
+
const lines = raw.trim().split("\n").filter((l) => l.trim());
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(line);
|
|
45
|
+
if (!parsed.typo || !parsed.line_num)
|
|
46
|
+
continue;
|
|
47
|
+
const corrections = parsed.corrections?.join(", ") || "no suggestions";
|
|
48
|
+
const message = `Typo: "${parsed.typo}" → ${corrections}`;
|
|
49
|
+
diagnostics.push({
|
|
50
|
+
id: `typos-${parsed.line_num}-${parsed.typo}`,
|
|
51
|
+
message,
|
|
52
|
+
filePath,
|
|
53
|
+
line: parsed.line_num,
|
|
54
|
+
column: 1, // typos-cli doesn't provide column, just byte offset
|
|
55
|
+
severity: "warning",
|
|
56
|
+
semantic: "warning",
|
|
57
|
+
tool: "typos",
|
|
58
|
+
rule: "typo",
|
|
59
|
+
fixable: !!parsed.corrections?.length,
|
|
60
|
+
fixSuggestion: parsed.corrections?.[0],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Skip invalid JSON lines
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return diagnostics;
|
|
69
|
+
}
|
|
70
|
+
const spellcheckRunner = {
|
|
71
|
+
id: "spellcheck",
|
|
72
|
+
appliesTo: ["markdown"],
|
|
73
|
+
priority: 30, // Run after code quality checks (biome=10, slop=25)
|
|
74
|
+
enabledByDefault: true,
|
|
75
|
+
skipTestFiles: false, // Check docs in test files too
|
|
76
|
+
async run(ctx) {
|
|
77
|
+
// Skip if typos-cli is not installed
|
|
78
|
+
if (!typos.isAvailable(ctx.cwd || process.cwd())) {
|
|
79
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
80
|
+
}
|
|
81
|
+
// Run typos-cli with JSON output
|
|
82
|
+
// --format json: Output JSON Lines
|
|
83
|
+
// --exclude <pattern>: Could be used to exclude code blocks if needed
|
|
84
|
+
const args = ["--format", "json", ctx.filePath];
|
|
85
|
+
const result = safeSpawn(typos.getCommand(), args, {
|
|
86
|
+
timeout: 15000,
|
|
87
|
+
});
|
|
88
|
+
// typos-cli exits with code 2 if typos found, 0 if clean
|
|
89
|
+
const hasTypos = result.status === 2 || result.stdout?.trim();
|
|
90
|
+
if (!hasTypos) {
|
|
91
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
92
|
+
}
|
|
93
|
+
// Parse diagnostics
|
|
94
|
+
const raw = result.stdout + result.stderr;
|
|
95
|
+
const diagnostics = parseTyposOutput(raw, ctx.filePath);
|
|
96
|
+
if (diagnostics.length === 0) {
|
|
97
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
status: "failed",
|
|
101
|
+
diagnostics,
|
|
102
|
+
semantic: "warning",
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
export default spellcheckRunner;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for spellcheck runner (typos-cli)
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
function createMockContext(filePath) {
|
|
9
|
+
return {
|
|
10
|
+
filePath,
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
kind: "markdown",
|
|
13
|
+
autofix: false,
|
|
14
|
+
deltaMode: false,
|
|
15
|
+
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
16
|
+
pi: {},
|
|
17
|
+
hasTool: async () => false,
|
|
18
|
+
log: () => { },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
describe("spellcheck runner", () => {
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
it("should have correct runner definition", async () => {
|
|
24
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
25
|
+
const runner = spellcheckModule.default;
|
|
26
|
+
expect(runner.id).toBe("spellcheck");
|
|
27
|
+
expect(runner.appliesTo).toEqual(["markdown"]);
|
|
28
|
+
expect(runner.priority).toBe(30);
|
|
29
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
30
|
+
expect(runner.skipTestFiles).toBe(false); // Check docs in test files too
|
|
31
|
+
});
|
|
32
|
+
it("should detect typos-cli availability", () => {
|
|
33
|
+
const { spawnSync } = require("node:child_process");
|
|
34
|
+
const result = spawnSync("typos", ["--version"], {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
timeout: 10000,
|
|
37
|
+
shell: true,
|
|
38
|
+
});
|
|
39
|
+
expect(result.error || result.status !== 0 ? "not available" : "available").toBeTruthy(); // May or may not be installed
|
|
40
|
+
});
|
|
41
|
+
it("should detect typos in markdown content", async () => {
|
|
42
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_test_${Date.now()}.md`);
|
|
43
|
+
fs.writeFileSync(tmpFile, `# README
|
|
44
|
+
|
|
45
|
+
This is a documnet about recieving data.
|
|
46
|
+
The seperation of concerns is important.
|
|
47
|
+
`);
|
|
48
|
+
try {
|
|
49
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
50
|
+
const runner = spellcheckModule.default;
|
|
51
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
52
|
+
// If typos-cli is installed, should detect typos
|
|
53
|
+
// If not installed, will be skipped
|
|
54
|
+
if (result.status !== "skipped") {
|
|
55
|
+
// Should detect at least "documnet" and "recieving"
|
|
56
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
57
|
+
expect(result.diagnostics.some((d) => d.tool === "typos" &&
|
|
58
|
+
(d.message.includes("documnet") ||
|
|
59
|
+
d.message.includes("recieving") ||
|
|
60
|
+
d.message.includes("seperation")))).toBe(true);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
if (fs.existsSync(tmpFile)) {
|
|
65
|
+
fs.unlinkSync(tmpFile);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("should suggest corrections for typos", async () => {
|
|
70
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_fix_${Date.now()}.md`);
|
|
71
|
+
fs.writeFileSync(tmpFile, `# Test
|
|
72
|
+
|
|
73
|
+
This is a recieving test.
|
|
74
|
+
`);
|
|
75
|
+
try {
|
|
76
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
77
|
+
const runner = spellcheckModule.default;
|
|
78
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
79
|
+
if (result.status !== "skipped" && result.diagnostics.length > 0) {
|
|
80
|
+
// Should have fix suggestions
|
|
81
|
+
const fixableDiags = result.diagnostics.filter((d) => d.fixable);
|
|
82
|
+
expect(fixableDiags.length).toBeGreaterThanOrEqual(1);
|
|
83
|
+
expect(fixableDiags.some((d) => d.fixSuggestion?.toLowerCase().includes("receive"))).toBe(true);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
if (fs.existsSync(tmpFile)) {
|
|
88
|
+
fs.unlinkSync(tmpFile);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
it("should pass clean markdown files", async () => {
|
|
93
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_ok_${Date.now()}.md`);
|
|
94
|
+
fs.writeFileSync(tmpFile, `# Clean README
|
|
95
|
+
|
|
96
|
+
This is a correct document about receiving data.
|
|
97
|
+
The separation of concerns is important.
|
|
98
|
+
All spelling is proper in this file.
|
|
99
|
+
`);
|
|
100
|
+
try {
|
|
101
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
102
|
+
const runner = spellcheckModule.default;
|
|
103
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
104
|
+
if (result.status !== "skipped") {
|
|
105
|
+
// Should have no typos
|
|
106
|
+
expect(result.diagnostics.length).toBe(0);
|
|
107
|
+
expect(result.status).toBe("succeeded");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (fs.existsSync(tmpFile)) {
|
|
112
|
+
fs.unlinkSync(tmpFile);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
it("should handle JSON parse errors gracefully", async () => {
|
|
117
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_json_${Date.now()}.md`);
|
|
118
|
+
fs.writeFileSync(tmpFile, `# Test\n\nSimple file.`);
|
|
119
|
+
try {
|
|
120
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
121
|
+
const runner = spellcheckModule.default;
|
|
122
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
123
|
+
// Should not crash on JSON parse issues
|
|
124
|
+
expect(["succeeded", "failed", "skipped"]).toContain(result.status);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (fs.existsSync(tmpFile)) {
|
|
128
|
+
fs.unlinkSync(tmpFile);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
it("should skip when typos-cli is not available", async () => {
|
|
133
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_skip_${Date.now()}.md`);
|
|
134
|
+
fs.writeFileSync(tmpFile, `# Test\n\nContent with typo: recieve.`);
|
|
135
|
+
try {
|
|
136
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
137
|
+
const runner = spellcheckModule.default;
|
|
138
|
+
// Check if typos is available
|
|
139
|
+
const { spawnSync } = require("node:child_process");
|
|
140
|
+
const checkResult = spawnSync("typos", ["--version"], {
|
|
141
|
+
encoding: "utf-8",
|
|
142
|
+
timeout: 5000,
|
|
143
|
+
shell: true,
|
|
144
|
+
});
|
|
145
|
+
const isAvailable = !checkResult.error && checkResult.status === 0;
|
|
146
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
147
|
+
if (!isAvailable) {
|
|
148
|
+
expect(result.status).toBe("skipped");
|
|
149
|
+
expect(result.diagnostics).toHaveLength(0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
if (fs.existsSync(tmpFile)) {
|
|
154
|
+
fs.unlinkSync(tmpFile);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|