effect-v4-audit 0.1.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/README.md +44 -0
- package/bin.js +2 -0
- package/package.json +42 -0
- package/src/analyze.ts +107 -0
- package/src/bin.ts +157 -0
- package/src/index.ts +6 -0
- package/src/line-starts.ts +47 -0
- package/src/render.ts +25 -0
- package/src/rules.ts +132 -0
- package/src/types.ts +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# effect-v4-audit
|
|
2
|
+
|
|
3
|
+
Deterministic CLI audit for v3-era Effect APIs in Effect v4 migrations.
|
|
4
|
+
|
|
5
|
+
## Install (local package)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd /Users/af/effect-cli-ce/effect-smol/packages/tools/effect-v4-audit
|
|
9
|
+
bun install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
After install, run:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun x effect-v4-audit --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
effect-v4-audit \
|
|
22
|
+
--cwd . \
|
|
23
|
+
--pattern "**/*.{ts,tsx,mts,cts}" \
|
|
24
|
+
--ignore "**/*.gen.ts" \
|
|
25
|
+
--format table \
|
|
26
|
+
--fail-on-findings
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Output
|
|
30
|
+
|
|
31
|
+
- `table`: human-readable findings with path, line/column, and migration suggestion.
|
|
32
|
+
- `json`: stable machine-readable report for CI.
|
|
33
|
+
|
|
34
|
+
## Guarantees
|
|
35
|
+
|
|
36
|
+
- deterministic file ordering and finding ordering
|
|
37
|
+
- deterministic summary counters and per-rule map ordering
|
|
38
|
+
- exits with code `1` only when `--fail-on-findings` is passed and findings exist
|
|
39
|
+
|
|
40
|
+
## Limitations
|
|
41
|
+
|
|
42
|
+
- rule-based text scan; does not parse full TypeScript AST
|
|
43
|
+
- may flag strings/comments containing legacy API text
|
|
44
|
+
- rules are intentionally conservative and not a full migration codemod
|
package/bin.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "effect-v4-audit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"bun": ">=1.3.0"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"description": "Deterministic Effect v4 migration audit CLI",
|
|
10
|
+
"homepage": "https://effect.website",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/Effect-TS/effect-smol.git",
|
|
14
|
+
"directory": "packages/tools/effect-v4-audit"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": [],
|
|
17
|
+
"bin": {
|
|
18
|
+
"effect-v4-audit": "./bin.js"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/index.ts",
|
|
22
|
+
"./package.json": "./package.json",
|
|
23
|
+
"./*": "./src/*.ts"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin.js",
|
|
27
|
+
"src/**/*.ts",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"audit": "bun run ./src/bin.ts",
|
|
32
|
+
"test": "bun test",
|
|
33
|
+
"check": "bun test --bail",
|
|
34
|
+
"coverage": "bun test --coverage"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"glob": "^13.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/analyze.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { computeLineStarts, indexToLineColumn } from "./line-starts.ts"
|
|
2
|
+
import { defaultV4AuditRules } from "./rules.ts"
|
|
3
|
+
import type { V4AuditFinding, V4AuditReport, V4AuditRule, V4AuditSourceFile, V4AuditSummary } from "./types.ts"
|
|
4
|
+
|
|
5
|
+
const normalizePath = (path: string): string => path.split("\\").join("/")
|
|
6
|
+
|
|
7
|
+
const asGlobalRegExp = (pattern: RegExp): RegExp => {
|
|
8
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
|
|
9
|
+
return new RegExp(pattern.source, flags)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const compareFindings = (left: V4AuditFinding, right: V4AuditFinding): number =>
|
|
13
|
+
left.path.localeCompare(right.path) ||
|
|
14
|
+
left.line - right.line ||
|
|
15
|
+
left.column - right.column ||
|
|
16
|
+
left.ruleId.localeCompare(right.ruleId) ||
|
|
17
|
+
left.match.localeCompare(right.match)
|
|
18
|
+
|
|
19
|
+
const compareRules = (left: V4AuditRule, right: V4AuditRule): number => left.id.localeCompare(right.id)
|
|
20
|
+
|
|
21
|
+
const scanSourceForRule = (
|
|
22
|
+
source: V4AuditSourceFile,
|
|
23
|
+
rule: V4AuditRule
|
|
24
|
+
): ReadonlyArray<V4AuditFinding> => {
|
|
25
|
+
const path = normalizePath(source.path)
|
|
26
|
+
const lineStarts = computeLineStarts(source.content)
|
|
27
|
+
const pattern = asGlobalRegExp(rule.pattern)
|
|
28
|
+
const findings: Array<V4AuditFinding> = []
|
|
29
|
+
|
|
30
|
+
let match: RegExpExecArray | null = null
|
|
31
|
+
while ((match = pattern.exec(source.content)) !== null) {
|
|
32
|
+
const position = indexToLineColumn(lineStarts, match.index)
|
|
33
|
+
findings.push({
|
|
34
|
+
ruleId: rule.id,
|
|
35
|
+
severity: rule.severity,
|
|
36
|
+
message: rule.message,
|
|
37
|
+
suggestion: rule.suggestion,
|
|
38
|
+
path,
|
|
39
|
+
line: position.line,
|
|
40
|
+
column: position.column,
|
|
41
|
+
match: match[0]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (match[0].length === 0) {
|
|
45
|
+
pattern.lastIndex = pattern.lastIndex + 1
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return findings
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const buildByRuleSummary = (findings: ReadonlyArray<V4AuditFinding>): Readonly<Record<string, number>> => {
|
|
53
|
+
const summary = new Map<string, number>()
|
|
54
|
+
for (const finding of findings) {
|
|
55
|
+
summary.set(finding.ruleId, (summary.get(finding.ruleId) ?? 0) + 1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const byRule: Record<string, number> = {}
|
|
59
|
+
for (const [ruleId, count] of [...summary.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
60
|
+
byRule[ruleId] = count
|
|
61
|
+
}
|
|
62
|
+
return byRule
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const buildSummary = (
|
|
66
|
+
filesScanned: number,
|
|
67
|
+
findings: ReadonlyArray<V4AuditFinding>
|
|
68
|
+
): V4AuditSummary => {
|
|
69
|
+
let errorCount = 0
|
|
70
|
+
let warningCount = 0
|
|
71
|
+
|
|
72
|
+
const filesWithFindings = new Set<string>()
|
|
73
|
+
for (const finding of findings) {
|
|
74
|
+
filesWithFindings.add(finding.path)
|
|
75
|
+
if (finding.severity === "error") {
|
|
76
|
+
errorCount++
|
|
77
|
+
} else {
|
|
78
|
+
warningCount++
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
filesScanned,
|
|
84
|
+
filesWithFindings: filesWithFindings.size,
|
|
85
|
+
findingCount: findings.length,
|
|
86
|
+
errorCount,
|
|
87
|
+
warningCount,
|
|
88
|
+
byRule: buildByRuleSummary(findings)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const analyzeV4Sources = (options: {
|
|
93
|
+
readonly sources: ReadonlyArray<V4AuditSourceFile>
|
|
94
|
+
readonly rules?: ReadonlyArray<V4AuditRule> | undefined
|
|
95
|
+
}): V4AuditReport => {
|
|
96
|
+
const rules = [...(options.rules ?? defaultV4AuditRules)].sort(compareRules)
|
|
97
|
+
const sources = [...options.sources].sort((a, b) => normalizePath(a.path).localeCompare(normalizePath(b.path)))
|
|
98
|
+
|
|
99
|
+
const findings = sources.flatMap((source) => rules.flatMap((rule) => scanSourceForRule(source, rule)))
|
|
100
|
+
findings.sort(compareFindings)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
version: 1,
|
|
104
|
+
summary: buildSummary(sources.length, findings),
|
|
105
|
+
findings
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { glob } from "glob"
|
|
6
|
+
import { analyzeV4Sources } from "./analyze.ts"
|
|
7
|
+
import { renderV4AuditJson, renderV4AuditTable } from "./render.ts"
|
|
8
|
+
|
|
9
|
+
interface CliOptions {
|
|
10
|
+
readonly cwd: string
|
|
11
|
+
readonly pattern: string
|
|
12
|
+
readonly ignore: ReadonlyArray<string>
|
|
13
|
+
readonly format: "table" | "json"
|
|
14
|
+
readonly failOnFindings: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const defaultIgnorePatterns = [
|
|
18
|
+
"**/node_modules/**",
|
|
19
|
+
"**/dist/**",
|
|
20
|
+
"**/coverage/**",
|
|
21
|
+
"**/.git/**",
|
|
22
|
+
"**/.turbo/**"
|
|
23
|
+
] as const
|
|
24
|
+
|
|
25
|
+
const usage = (): string => [
|
|
26
|
+
"effect-v4-audit",
|
|
27
|
+
"",
|
|
28
|
+
"Usage:",
|
|
29
|
+
" effect-v4-audit [--cwd <dir>] [--pattern <glob>] [--ignore <glob>] [--format table|json] [--fail-on-findings]",
|
|
30
|
+
"",
|
|
31
|
+
"Options:",
|
|
32
|
+
" -c, --cwd <dir> Directory to scan (default: .)",
|
|
33
|
+
" -p, --pattern <glob> Glob for files to scan (default: **/*.{ts,tsx,mts,cts})",
|
|
34
|
+
" -i, --ignore <glob> Glob to ignore (repeat for multiple values)",
|
|
35
|
+
" -f, --format <table|json> Output format (default: table)",
|
|
36
|
+
" --fail-on-findings Exit with code 1 when findings exist",
|
|
37
|
+
" -h, --help Show this help"
|
|
38
|
+
].join("\n")
|
|
39
|
+
|
|
40
|
+
const parseNextValue = (args: ReadonlyArray<string>, index: number, flag: string): string => {
|
|
41
|
+
const value = args[index + 1]
|
|
42
|
+
if (!value || value.startsWith("-")) {
|
|
43
|
+
throw new Error(`Missing value for ${flag}`)
|
|
44
|
+
}
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const parseCliArgs = (args: ReadonlyArray<string>): CliOptions | { readonly help: true } => {
|
|
49
|
+
let cwd = "."
|
|
50
|
+
let pattern = "**/*.{ts,tsx,mts,cts}"
|
|
51
|
+
const ignore: Array<string> = []
|
|
52
|
+
let format: "table" | "json" = "table"
|
|
53
|
+
let failOnFindings = false
|
|
54
|
+
|
|
55
|
+
for (let index = 0; index < args.length; index++) {
|
|
56
|
+
const arg = args[index]
|
|
57
|
+
|
|
58
|
+
switch (arg) {
|
|
59
|
+
case "-h":
|
|
60
|
+
case "--help":
|
|
61
|
+
return { help: true }
|
|
62
|
+
case "-c":
|
|
63
|
+
case "--cwd":
|
|
64
|
+
cwd = parseNextValue(args, index, arg)
|
|
65
|
+
index++
|
|
66
|
+
break
|
|
67
|
+
case "-p":
|
|
68
|
+
case "--pattern":
|
|
69
|
+
pattern = parseNextValue(args, index, arg)
|
|
70
|
+
index++
|
|
71
|
+
break
|
|
72
|
+
case "-i":
|
|
73
|
+
case "--ignore":
|
|
74
|
+
ignore.push(parseNextValue(args, index, arg))
|
|
75
|
+
index++
|
|
76
|
+
break
|
|
77
|
+
case "-f":
|
|
78
|
+
case "--format": {
|
|
79
|
+
const value = parseNextValue(args, index, arg)
|
|
80
|
+
if (value !== "table" && value !== "json") {
|
|
81
|
+
throw new Error(`Invalid format: ${value}`)
|
|
82
|
+
}
|
|
83
|
+
format = value
|
|
84
|
+
index++
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
case "--fail-on-findings":
|
|
88
|
+
failOnFindings = true
|
|
89
|
+
break
|
|
90
|
+
default:
|
|
91
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
cwd,
|
|
97
|
+
pattern,
|
|
98
|
+
ignore,
|
|
99
|
+
format,
|
|
100
|
+
failOnFindings
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const toPosix = (file: string): string => file.split(path.sep).join("/")
|
|
105
|
+
|
|
106
|
+
const readSources = async (options: CliOptions): Promise<ReadonlyArray<{ readonly path: string; readonly content: string }>> => {
|
|
107
|
+
const root = path.resolve(options.cwd)
|
|
108
|
+
const files = await glob(options.pattern, {
|
|
109
|
+
cwd: root,
|
|
110
|
+
dot: false,
|
|
111
|
+
follow: false,
|
|
112
|
+
nodir: true,
|
|
113
|
+
ignore: [...defaultIgnorePatterns, ...options.ignore]
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const sorted = files.sort((a, b) => toPosix(a).localeCompare(toPosix(b)))
|
|
117
|
+
|
|
118
|
+
return Promise.all(
|
|
119
|
+
sorted.map(async (file) => {
|
|
120
|
+
const absolute = path.join(root, file)
|
|
121
|
+
const content = await readFile(absolute, "utf8")
|
|
122
|
+
return {
|
|
123
|
+
path: toPosix(file),
|
|
124
|
+
content
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const runCli = async (args: ReadonlyArray<string>): Promise<number> => {
|
|
131
|
+
const parsed = parseCliArgs(args)
|
|
132
|
+
if ("help" in parsed) {
|
|
133
|
+
console.log(usage())
|
|
134
|
+
return 0
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const sources = await readSources(parsed)
|
|
138
|
+
const report = analyzeV4Sources({ sources })
|
|
139
|
+
const output = parsed.format === "json" ? renderV4AuditJson(report) : renderV4AuditTable(report)
|
|
140
|
+
console.log(output)
|
|
141
|
+
|
|
142
|
+
if (parsed.failOnFindings && report.summary.findingCount > 0) {
|
|
143
|
+
return 1
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return 0
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (import.meta.main) {
|
|
150
|
+
const exitCode = await runCli(process.argv.slice(2)).catch((error: unknown) => {
|
|
151
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
152
|
+
console.error(message)
|
|
153
|
+
return 1
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
process.exitCode = exitCode
|
|
157
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { analyzeV4Sources } from "./analyze.ts"
|
|
2
|
+
import { defaultV4AuditRules } from "./rules.ts"
|
|
3
|
+
import { renderV4AuditJson, renderV4AuditTable } from "./render.ts"
|
|
4
|
+
|
|
5
|
+
export * from "./types.ts"
|
|
6
|
+
export { analyzeV4Sources, defaultV4AuditRules, renderV4AuditJson, renderV4AuditTable }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface LineColumn {
|
|
2
|
+
readonly line: number
|
|
3
|
+
readonly column: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const computeLineStarts = (content: string): ReadonlyArray<number> => {
|
|
7
|
+
const starts: Array<number> = [0]
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < content.length; i++) {
|
|
10
|
+
if (content[i] === "\n") {
|
|
11
|
+
starts.push(i + 1)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return starts
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const indexToLineColumn = (lineStarts: ReadonlyArray<number>, index: number): LineColumn => {
|
|
19
|
+
let low = 0
|
|
20
|
+
let high = lineStarts.length - 1
|
|
21
|
+
|
|
22
|
+
while (low <= high) {
|
|
23
|
+
const mid = Math.floor((low + high) / 2)
|
|
24
|
+
const start = lineStarts[mid]
|
|
25
|
+
const nextStart = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : Number.POSITIVE_INFINITY
|
|
26
|
+
|
|
27
|
+
if (index < start) {
|
|
28
|
+
high = mid - 1
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (index >= nextStart) {
|
|
33
|
+
low = mid + 1
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
line: mid + 1,
|
|
39
|
+
column: index - start + 1
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
line: lineStarts.length,
|
|
45
|
+
column: 1
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { V4AuditReport } from "./types.ts"
|
|
2
|
+
|
|
3
|
+
export const renderV4AuditJson = (report: V4AuditReport): string => JSON.stringify(report, null, 2)
|
|
4
|
+
|
|
5
|
+
export const renderV4AuditTable = (report: V4AuditReport): string => {
|
|
6
|
+
if (report.summary.findingCount === 0) {
|
|
7
|
+
return `No v4-audit findings. Files scanned: ${report.summary.filesScanned}.`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const lines: Array<string> = [
|
|
11
|
+
`Findings: ${report.summary.findingCount} (errors: ${report.summary.errorCount}, warnings: ${report.summary.warningCount})`,
|
|
12
|
+
`Files scanned: ${report.summary.filesScanned}, files with findings: ${report.summary.filesWithFindings}`,
|
|
13
|
+
""
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
for (const finding of report.findings) {
|
|
17
|
+
lines.push(`[${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}:${finding.line}:${finding.column}`)
|
|
18
|
+
lines.push(` ${finding.message}`)
|
|
19
|
+
lines.push(` Suggestion: ${finding.suggestion}`)
|
|
20
|
+
lines.push(` Match: ${JSON.stringify(finding.match)}`)
|
|
21
|
+
lines.push("")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return lines.join("\n")
|
|
25
|
+
}
|
package/src/rules.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { V4AuditRule } from "./types.ts"
|
|
2
|
+
|
|
3
|
+
const errorRule = (
|
|
4
|
+
id: string,
|
|
5
|
+
pattern: RegExp,
|
|
6
|
+
message: string,
|
|
7
|
+
suggestion: string
|
|
8
|
+
): V4AuditRule => ({
|
|
9
|
+
id,
|
|
10
|
+
severity: "error",
|
|
11
|
+
pattern,
|
|
12
|
+
message,
|
|
13
|
+
suggestion
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const warningRule = (
|
|
17
|
+
id: string,
|
|
18
|
+
pattern: RegExp,
|
|
19
|
+
message: string,
|
|
20
|
+
suggestion: string
|
|
21
|
+
): V4AuditRule => ({
|
|
22
|
+
id,
|
|
23
|
+
severity: "warning",
|
|
24
|
+
pattern,
|
|
25
|
+
message,
|
|
26
|
+
suggestion
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const defaultV4AuditRules: ReadonlyArray<V4AuditRule> = [
|
|
30
|
+
errorRule(
|
|
31
|
+
"legacy-context-generic-tag",
|
|
32
|
+
/\bContext\.GenericTag\b/g,
|
|
33
|
+
"Context.GenericTag is a v3 API.",
|
|
34
|
+
"Use ServiceMap.Service instead."
|
|
35
|
+
),
|
|
36
|
+
errorRule(
|
|
37
|
+
"legacy-context-tag",
|
|
38
|
+
/\bContext\.Tag\b/g,
|
|
39
|
+
"Context.Tag is a v3 API.",
|
|
40
|
+
"Use ServiceMap.Service instead."
|
|
41
|
+
),
|
|
42
|
+
errorRule(
|
|
43
|
+
"legacy-context-reference",
|
|
44
|
+
/\bContext\.Reference\b/g,
|
|
45
|
+
"Context.Reference is a v3 API.",
|
|
46
|
+
"Use ServiceMap.Reference instead."
|
|
47
|
+
),
|
|
48
|
+
errorRule(
|
|
49
|
+
"legacy-effect-tag",
|
|
50
|
+
/\bEffect\.Tag\s*\(/g,
|
|
51
|
+
"Effect.Tag is a v3 API.",
|
|
52
|
+
"Use ServiceMap.Service class syntax or function syntax."
|
|
53
|
+
),
|
|
54
|
+
errorRule(
|
|
55
|
+
"legacy-effect-service",
|
|
56
|
+
/\bEffect\.Service\s*\(/g,
|
|
57
|
+
"Effect.Service is a v3 API.",
|
|
58
|
+
"Use ServiceMap.Service plus explicit Layer.effect wiring."
|
|
59
|
+
),
|
|
60
|
+
errorRule(
|
|
61
|
+
"legacy-fiberref",
|
|
62
|
+
/\bFiberRef\./g,
|
|
63
|
+
"FiberRef APIs were replaced in v4.",
|
|
64
|
+
"Use References.* values or ServiceMap.Reference."
|
|
65
|
+
),
|
|
66
|
+
errorRule(
|
|
67
|
+
"legacy-scope-extend",
|
|
68
|
+
/\bScope\.extend\b/g,
|
|
69
|
+
"Scope.extend was renamed in v4.",
|
|
70
|
+
"Use Scope.provide."
|
|
71
|
+
),
|
|
72
|
+
errorRule(
|
|
73
|
+
"legacy-catch-all",
|
|
74
|
+
/\bEffect\.catchAll\b/g,
|
|
75
|
+
"Effect.catchAll was renamed in v4.",
|
|
76
|
+
"Use Effect.catch."
|
|
77
|
+
),
|
|
78
|
+
errorRule(
|
|
79
|
+
"legacy-catch-all-cause",
|
|
80
|
+
/\bEffect\.catchAllCause\b/g,
|
|
81
|
+
"Effect.catchAllCause was renamed in v4.",
|
|
82
|
+
"Use Effect.catchCause."
|
|
83
|
+
),
|
|
84
|
+
errorRule(
|
|
85
|
+
"legacy-catch-all-defect",
|
|
86
|
+
/\bEffect\.catchAllDefect\b/g,
|
|
87
|
+
"Effect.catchAllDefect was renamed in v4.",
|
|
88
|
+
"Use Effect.catchDefect."
|
|
89
|
+
),
|
|
90
|
+
errorRule(
|
|
91
|
+
"legacy-catch-some",
|
|
92
|
+
/\bEffect\.catchSome\b/g,
|
|
93
|
+
"Effect.catchSome was renamed in v4.",
|
|
94
|
+
"Use Effect.catchFilter."
|
|
95
|
+
),
|
|
96
|
+
errorRule(
|
|
97
|
+
"legacy-catch-some-cause",
|
|
98
|
+
/\bEffect\.catchSomeCause\b/g,
|
|
99
|
+
"Effect.catchSomeCause was renamed in v4.",
|
|
100
|
+
"Use Effect.catchCauseFilter."
|
|
101
|
+
),
|
|
102
|
+
errorRule(
|
|
103
|
+
"legacy-catch-some-defect",
|
|
104
|
+
/\bEffect\.catchSomeDefect\b/g,
|
|
105
|
+
"Effect.catchSomeDefect was removed in v4.",
|
|
106
|
+
"Use Effect.catchDefect or Effect.catchCause depending on intent."
|
|
107
|
+
),
|
|
108
|
+
errorRule(
|
|
109
|
+
"legacy-fork",
|
|
110
|
+
/\bEffect\.fork\s*\(/g,
|
|
111
|
+
"Effect.fork was renamed in v4.",
|
|
112
|
+
"Use Effect.forkChild."
|
|
113
|
+
),
|
|
114
|
+
errorRule(
|
|
115
|
+
"legacy-fork-daemon",
|
|
116
|
+
/\bEffect\.forkDaemon\b/g,
|
|
117
|
+
"Effect.forkDaemon was renamed in v4.",
|
|
118
|
+
"Use Effect.forkDetach."
|
|
119
|
+
),
|
|
120
|
+
errorRule(
|
|
121
|
+
"legacy-equal-equivalence",
|
|
122
|
+
/\bEqual\.equivalence\b/g,
|
|
123
|
+
"Equal.equivalence was renamed in v4.",
|
|
124
|
+
"Use Equal.asEquivalence."
|
|
125
|
+
),
|
|
126
|
+
warningRule(
|
|
127
|
+
"unstable-effect-import",
|
|
128
|
+
/from\s+["']effect\/unstable\/[^"']+["']/g,
|
|
129
|
+
"Unstable module import detected.",
|
|
130
|
+
"Treat unstable APIs as non-semver-stable and isolate usage."
|
|
131
|
+
)
|
|
132
|
+
]
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type V4AuditSeverity = "error" | "warning"
|
|
2
|
+
|
|
3
|
+
export interface V4AuditRule {
|
|
4
|
+
readonly id: string
|
|
5
|
+
readonly severity: V4AuditSeverity
|
|
6
|
+
readonly pattern: RegExp
|
|
7
|
+
readonly message: string
|
|
8
|
+
readonly suggestion: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface V4AuditSourceFile {
|
|
12
|
+
readonly path: string
|
|
13
|
+
readonly content: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface V4AuditFinding {
|
|
17
|
+
readonly ruleId: string
|
|
18
|
+
readonly severity: V4AuditSeverity
|
|
19
|
+
readonly message: string
|
|
20
|
+
readonly suggestion: string
|
|
21
|
+
readonly path: string
|
|
22
|
+
readonly line: number
|
|
23
|
+
readonly column: number
|
|
24
|
+
readonly match: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface V4AuditSummary {
|
|
28
|
+
readonly filesScanned: number
|
|
29
|
+
readonly filesWithFindings: number
|
|
30
|
+
readonly findingCount: number
|
|
31
|
+
readonly errorCount: number
|
|
32
|
+
readonly warningCount: number
|
|
33
|
+
readonly byRule: Readonly<Record<string, number>>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface V4AuditReport {
|
|
37
|
+
readonly version: 1
|
|
38
|
+
readonly summary: V4AuditSummary
|
|
39
|
+
readonly findings: ReadonlyArray<V4AuditFinding>
|
|
40
|
+
}
|