zodrift 0.1.2 → 0.1.3
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 +5 -1
- package/dist/cli.js +2 -1
- package/dist/commands/check.js +13 -0
- package/dist/commands/fix.js +1 -0
- package/dist/core/checker.js +14 -2
- package/dist/core/semantic-checker.d.ts +10 -0
- package/dist/core/semantic-checker.js +190 -0
- package/dist/core/types.d.ts +5 -1
- package/dist/reporters/json.js +2 -0
- package/dist/reporters/pretty.js +3 -0
- package/dist/reporters/sarif.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,11 +45,12 @@ What it catches:
|
|
|
45
45
|
- extra fields
|
|
46
46
|
- required vs optional mismatch
|
|
47
47
|
- basic type mismatch
|
|
48
|
+
- semantic mismatch when your TS type is not assignable to `z.input<typeof Schema>` / `z.output<typeof Schema>` (optional mode)
|
|
48
49
|
|
|
49
50
|
CI quick start:
|
|
50
51
|
|
|
51
52
|
```yaml
|
|
52
|
-
- run: npx zodrift check --pattern "src/**/*.{ts,tsx}"
|
|
53
|
+
- run: npx zodrift check --pattern "src/**/*.{ts,tsx}" --semantics both
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
Exit codes:
|
|
@@ -63,6 +64,9 @@ Useful commands:
|
|
|
63
64
|
# Check current project
|
|
64
65
|
npx zodrift check --pattern "src/**/*.{ts,tsx}"
|
|
65
66
|
|
|
67
|
+
# Add semantic compatibility checks for z.input/z.output
|
|
68
|
+
npx zodrift check --pattern "src/**/*.{ts,tsx}" --semantics both
|
|
69
|
+
|
|
66
70
|
# Machine-readable report for CI artifacts
|
|
67
71
|
npx zodrift check --format json --out reports/zodrift.json
|
|
68
72
|
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ function printHelp() {
|
|
|
9
9
|
process.stdout.write(`zodrift
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
zodrift check [--pattern <glob>] [--format pretty|json|sarif] [--out <file>] [--changed]
|
|
12
|
+
zodrift check [--pattern <glob>] [--format pretty|json|sarif] [--semantics off|input|output|both] [--out <file>] [--changed]
|
|
13
13
|
zodrift fix [--pattern <glob>] [--target schema] [--dry-run] [--write]
|
|
14
14
|
zodrift codegen [--from ts|zod] [--pattern <glob>] [--out-dir <dir>] [--write]
|
|
15
15
|
zodrift openapi [--pattern <glob>] [--out <file>]
|
|
@@ -18,6 +18,7 @@ Usage:
|
|
|
18
18
|
Examples:
|
|
19
19
|
npx zodrift check
|
|
20
20
|
npx zodrift check --pattern "examples/**/*.ts"
|
|
21
|
+
npx zodrift check --semantics both
|
|
21
22
|
npx zodrift check --format json --out reports/zodrift.json
|
|
22
23
|
npx zodrift fix --pattern "src/**/*.ts" --dry-run
|
|
23
24
|
`);
|
package/dist/commands/check.js
CHANGED
|
@@ -3,16 +3,29 @@ import path from "node:path";
|
|
|
3
3
|
import { runCheck } from "../core/checker.js";
|
|
4
4
|
import { renderOutput } from "../reporters/index.js";
|
|
5
5
|
import { flagBoolean, flagNumber, flagString } from "../utils/args.js";
|
|
6
|
+
function parseSemanticsMode(value) {
|
|
7
|
+
if (value === "off" || value === "input" || value === "output" || value === "both") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
6
12
|
export function runCheckCommand(flags) {
|
|
7
13
|
const cwd = process.cwd();
|
|
8
14
|
const pattern = flagString(flags, "pattern", "**/*.{ts,tsx}");
|
|
9
15
|
const format = flagString(flags, "format", "pretty");
|
|
16
|
+
const semanticsRaw = flagString(flags, "semantics", "off");
|
|
17
|
+
const semantics = parseSemanticsMode(semanticsRaw);
|
|
18
|
+
if (!semantics) {
|
|
19
|
+
process.stderr.write(`Invalid --semantics value: ${semanticsRaw}. Use one of: off, input, output, both.\n`);
|
|
20
|
+
return 2;
|
|
21
|
+
}
|
|
10
22
|
const maxIssues = flagNumber(flags, "max-issues");
|
|
11
23
|
const changedOnly = flagBoolean(flags, "changed", false);
|
|
12
24
|
const result = runCheck({
|
|
13
25
|
cwd,
|
|
14
26
|
pattern,
|
|
15
27
|
format,
|
|
28
|
+
semantics,
|
|
16
29
|
maxIssues,
|
|
17
30
|
changedOnly,
|
|
18
31
|
});
|
package/dist/commands/fix.js
CHANGED
package/dist/core/checker.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { comparePair } from "./compare.js";
|
|
2
2
|
import { discoverSourceFiles } from "./file-discovery.js";
|
|
3
3
|
import { pairTypesWithSchemas } from "./pairing.js";
|
|
4
|
+
import { collectSemanticIssues, pairKey } from "./semantic-checker.js";
|
|
4
5
|
import { parseTypeDeclarations } from "./ts-parser.js";
|
|
5
6
|
import { parseSchemaDeclarations } from "./zod-parser.js";
|
|
6
7
|
export function runCheck(options) {
|
|
@@ -12,14 +13,23 @@ export function runCheck(options) {
|
|
|
12
13
|
const typeParsed = parseTypeDeclarations(files);
|
|
13
14
|
const schemaParsed = parseSchemaDeclarations(files);
|
|
14
15
|
const paired = pairTypesWithSchemas(typeParsed.declarations, schemaParsed.declarations);
|
|
16
|
+
const semantic = collectSemanticIssues({
|
|
17
|
+
cwd: options.cwd,
|
|
18
|
+
pairs: paired.pairs,
|
|
19
|
+
semantics: options.semantics,
|
|
20
|
+
});
|
|
15
21
|
const pairs = [];
|
|
16
22
|
let totalIssues = 0;
|
|
23
|
+
let semanticIssueCount = 0;
|
|
17
24
|
for (const pair of paired.pairs) {
|
|
18
|
-
const
|
|
25
|
+
const structuralIssues = comparePair(pair);
|
|
26
|
+
const semanticIssues = semantic.issuesByPair.get(pairKey(pair)) ?? [];
|
|
27
|
+
const issues = [...structuralIssues, ...semanticIssues];
|
|
19
28
|
const cappedIssues = options.maxIssues && options.maxIssues > 0
|
|
20
29
|
? issues.slice(0, options.maxIssues)
|
|
21
30
|
: issues;
|
|
22
31
|
totalIssues += cappedIssues.length;
|
|
32
|
+
semanticIssueCount += cappedIssues.filter((issue) => issue.kind === "semantic_mismatch").length;
|
|
23
33
|
pairs.push({
|
|
24
34
|
typeName: pair.typeDecl.name,
|
|
25
35
|
schemaName: pair.schemaDecl.name,
|
|
@@ -34,6 +44,8 @@ export function runCheck(options) {
|
|
|
34
44
|
checkedPairs: pairs.length,
|
|
35
45
|
unmatchedTypes: paired.unmatchedTypes,
|
|
36
46
|
unmatchedSchemas: paired.unmatchedSchemas,
|
|
37
|
-
|
|
47
|
+
semanticsMode: options.semantics,
|
|
48
|
+
semanticIssueCount,
|
|
49
|
+
errors: [...typeParsed.errors, ...schemaParsed.errors, ...semantic.errors],
|
|
38
50
|
};
|
|
39
51
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DriftIssue, Pairing, SemanticsMode } from "./types.js";
|
|
2
|
+
export declare function pairKey(pair: Pairing): string;
|
|
3
|
+
export declare function collectSemanticIssues(options: {
|
|
4
|
+
cwd: string;
|
|
5
|
+
pairs: Pairing[];
|
|
6
|
+
semantics: SemanticsMode;
|
|
7
|
+
}): {
|
|
8
|
+
issuesByPair: Map<string, DriftIssue[]>;
|
|
9
|
+
errors: string[];
|
|
10
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
const TYPE_ALIAS_NAME = "__ZodriftTypeAlias";
|
|
5
|
+
const INPUT_ALIAS_NAME = "__ZodriftInputAlias";
|
|
6
|
+
const OUTPUT_ALIAS_NAME = "__ZodriftOutputAlias";
|
|
7
|
+
const TYPE_IMPORT_NAME = "__ZodriftType";
|
|
8
|
+
const SCHEMA_IMPORT_NAME = "__ZodriftSchema";
|
|
9
|
+
function pairName(pair) {
|
|
10
|
+
return `${pair.typeDecl.name} ↔ ${pair.schemaDecl.name}`;
|
|
11
|
+
}
|
|
12
|
+
export function pairKey(pair) {
|
|
13
|
+
return `${path.resolve(pair.typeDecl.sourceFilePath)}::${pair.typeDecl.name}::${path.resolve(pair.schemaDecl.sourceFilePath)}::${pair.schemaDecl.name}`;
|
|
14
|
+
}
|
|
15
|
+
function loadCompilerOptions(cwd) {
|
|
16
|
+
const fallback = {
|
|
17
|
+
target: ts.ScriptTarget.ES2022,
|
|
18
|
+
module: ts.ModuleKind.NodeNext,
|
|
19
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
20
|
+
jsx: ts.JsxEmit.Preserve,
|
|
21
|
+
allowJs: false,
|
|
22
|
+
skipLibCheck: true,
|
|
23
|
+
strict: true,
|
|
24
|
+
exactOptionalPropertyTypes: true,
|
|
25
|
+
noEmit: true,
|
|
26
|
+
};
|
|
27
|
+
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
|
|
28
|
+
if (!configPath) {
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
const read = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
32
|
+
if (read.error) {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configPath));
|
|
36
|
+
return {
|
|
37
|
+
...parsed.options,
|
|
38
|
+
module: ts.ModuleKind.NodeNext,
|
|
39
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
40
|
+
jsx: parsed.options.jsx ?? ts.JsxEmit.Preserve,
|
|
41
|
+
rootDir: cwd,
|
|
42
|
+
strict: true,
|
|
43
|
+
exactOptionalPropertyTypes: true,
|
|
44
|
+
skipLibCheck: true,
|
|
45
|
+
noEmit: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function toImportSpecifier(fromFilePath, targetFilePath) {
|
|
49
|
+
const relative = path
|
|
50
|
+
.relative(path.dirname(fromFilePath), targetFilePath)
|
|
51
|
+
.split(path.sep)
|
|
52
|
+
.join("/");
|
|
53
|
+
const withDot = relative.startsWith(".") ? relative : `./${relative}`;
|
|
54
|
+
const withoutExt = withDot.replace(/\.[cm]?tsx?$/i, "");
|
|
55
|
+
return `${withoutExt}.js`;
|
|
56
|
+
}
|
|
57
|
+
function formatDiagnostic(diagnostic) {
|
|
58
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
59
|
+
if (!diagnostic.file || typeof diagnostic.start !== "number") {
|
|
60
|
+
return message;
|
|
61
|
+
}
|
|
62
|
+
const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
63
|
+
const filePath = path.resolve(diagnostic.file.fileName);
|
|
64
|
+
return `${filePath}:${pos.line + 1}:${pos.character + 1} ${message}`;
|
|
65
|
+
}
|
|
66
|
+
function typeText(checker, type) {
|
|
67
|
+
return checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope);
|
|
68
|
+
}
|
|
69
|
+
function compareEquivalence(checker, left, right) {
|
|
70
|
+
return {
|
|
71
|
+
leftToRight: checker.isTypeAssignableTo(left, right),
|
|
72
|
+
rightToLeft: checker.isTypeAssignableTo(right, left),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function makeSemanticIssue(params) {
|
|
76
|
+
return {
|
|
77
|
+
kind: "semantic_mismatch",
|
|
78
|
+
pairName: pairName(params.pair),
|
|
79
|
+
path: `(semantic:${params.mode})`,
|
|
80
|
+
message: params.message,
|
|
81
|
+
typeValue: params.typeValue,
|
|
82
|
+
schemaValue: params.schemaValue,
|
|
83
|
+
typeLocation: params.pair.typeDecl.location,
|
|
84
|
+
schemaLocation: params.pair.schemaDecl.location,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function buildSemanticProbeContent(pair, probeFilePath) {
|
|
88
|
+
const typeSpecifier = toImportSpecifier(probeFilePath, pair.typeDecl.sourceFilePath);
|
|
89
|
+
const schemaSpecifier = toImportSpecifier(probeFilePath, pair.schemaDecl.sourceFilePath);
|
|
90
|
+
return `import { z } from "zod";
|
|
91
|
+
import type { ${pair.typeDecl.name} as ${TYPE_IMPORT_NAME} } from "${typeSpecifier}";
|
|
92
|
+
import { ${pair.schemaDecl.name} as ${SCHEMA_IMPORT_NAME} } from "${schemaSpecifier}";
|
|
93
|
+
|
|
94
|
+
type ${TYPE_ALIAS_NAME} = ${TYPE_IMPORT_NAME};
|
|
95
|
+
type ${INPUT_ALIAS_NAME} = z.input<typeof ${SCHEMA_IMPORT_NAME}>;
|
|
96
|
+
type ${OUTPUT_ALIAS_NAME} = z.output<typeof ${SCHEMA_IMPORT_NAME}>;
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
function findAliasType(checker, sourceFile, aliasName) {
|
|
100
|
+
const alias = sourceFile.statements.find((statement) => ts.isTypeAliasDeclaration(statement) && statement.name.text === aliasName);
|
|
101
|
+
if (!alias) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return checker.getTypeAtLocation(alias);
|
|
105
|
+
}
|
|
106
|
+
export function collectSemanticIssues(options) {
|
|
107
|
+
const issuesByPair = new Map();
|
|
108
|
+
const errors = [];
|
|
109
|
+
if (options.semantics === "off" || options.pairs.length === 0) {
|
|
110
|
+
return { issuesByPair, errors };
|
|
111
|
+
}
|
|
112
|
+
const compilerOptions = loadCompilerOptions(options.cwd);
|
|
113
|
+
const tempDir = fs.mkdtempSync(path.join(options.cwd, ".zodrift-semantic-"));
|
|
114
|
+
const runInput = options.semantics === "input" || options.semantics === "both";
|
|
115
|
+
const runOutput = options.semantics === "output" || options.semantics === "both";
|
|
116
|
+
try {
|
|
117
|
+
for (let index = 0; index < options.pairs.length; index += 1) {
|
|
118
|
+
const pair = options.pairs[index];
|
|
119
|
+
const pairIssues = [];
|
|
120
|
+
const probeFilePath = path.join(tempDir, `probe-${index}.ts`);
|
|
121
|
+
fs.writeFileSync(probeFilePath, buildSemanticProbeContent(pair, probeFilePath), "utf8");
|
|
122
|
+
const program = ts.createProgram([probeFilePath], compilerOptions);
|
|
123
|
+
const diagnostics = ts
|
|
124
|
+
.getPreEmitDiagnostics(program)
|
|
125
|
+
.filter((diag) => diag.category === ts.DiagnosticCategory.Error);
|
|
126
|
+
if (diagnostics.length > 0) {
|
|
127
|
+
const message = formatDiagnostic(diagnostics[0]);
|
|
128
|
+
pairIssues.push(makeSemanticIssue({
|
|
129
|
+
pair,
|
|
130
|
+
mode: runInput ? "input" : "output",
|
|
131
|
+
message: `semantic check could not evaluate pair: ${message}`,
|
|
132
|
+
}));
|
|
133
|
+
issuesByPair.set(pairKey(pair), pairIssues);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const checker = program.getTypeChecker();
|
|
137
|
+
const probeSource = program.getSourceFile(probeFilePath);
|
|
138
|
+
if (!probeSource) {
|
|
139
|
+
pairIssues.push(makeSemanticIssue({
|
|
140
|
+
pair,
|
|
141
|
+
mode: runInput ? "input" : "output",
|
|
142
|
+
message: "semantic check could not load probe source file",
|
|
143
|
+
}));
|
|
144
|
+
issuesByPair.set(pairKey(pair), pairIssues);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const typeType = findAliasType(checker, probeSource, TYPE_ALIAS_NAME);
|
|
148
|
+
const inputType = findAliasType(checker, probeSource, INPUT_ALIAS_NAME);
|
|
149
|
+
const outputType = findAliasType(checker, probeSource, OUTPUT_ALIAS_NAME);
|
|
150
|
+
if (!typeType || !inputType || !outputType) {
|
|
151
|
+
pairIssues.push(makeSemanticIssue({
|
|
152
|
+
pair,
|
|
153
|
+
mode: runInput ? "input" : "output",
|
|
154
|
+
message: "semantic check could not infer probe alias types",
|
|
155
|
+
}));
|
|
156
|
+
issuesByPair.set(pairKey(pair), pairIssues);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const evaluate = (mode, schemaType) => {
|
|
160
|
+
const comparison = compareEquivalence(checker, typeType, schemaType);
|
|
161
|
+
if (comparison.leftToRight) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
pairIssues.push(makeSemanticIssue({
|
|
165
|
+
pair,
|
|
166
|
+
mode,
|
|
167
|
+
message: `semantic mismatch (${mode}): ${pair.typeDecl.name} is not assignable to z.${mode}<typeof ${pair.schemaDecl.name}> (type->schema=${comparison.leftToRight}, schema->type=${comparison.rightToLeft})`,
|
|
168
|
+
typeValue: typeText(checker, typeType),
|
|
169
|
+
schemaValue: typeText(checker, schemaType),
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
if (runInput) {
|
|
173
|
+
evaluate("input", inputType);
|
|
174
|
+
}
|
|
175
|
+
if (runOutput) {
|
|
176
|
+
evaluate("output", outputType);
|
|
177
|
+
}
|
|
178
|
+
if (pairIssues.length > 0) {
|
|
179
|
+
issuesByPair.set(pairKey(pair), pairIssues);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
errors.push(`Failed semantic check pass: ${String(error)}`);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
return { issuesByPair, errors };
|
|
190
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface SchemaDeclaration {
|
|
|
65
65
|
sourceFilePath: string;
|
|
66
66
|
objectNodeText?: string;
|
|
67
67
|
}
|
|
68
|
-
export type DriftIssueKind = "missing_in_schema" | "extra_in_schema" | "optional_mismatch" | "type_mismatch";
|
|
68
|
+
export type DriftIssueKind = "missing_in_schema" | "extra_in_schema" | "optional_mismatch" | "type_mismatch" | "semantic_mismatch";
|
|
69
69
|
export interface DriftIssue {
|
|
70
70
|
kind: DriftIssueKind;
|
|
71
71
|
pairName: string;
|
|
@@ -89,6 +89,8 @@ export interface CheckResult {
|
|
|
89
89
|
checkedPairs: number;
|
|
90
90
|
unmatchedTypes: string[];
|
|
91
91
|
unmatchedSchemas: string[];
|
|
92
|
+
semanticsMode: SemanticsMode;
|
|
93
|
+
semanticIssueCount: number;
|
|
92
94
|
errors: string[];
|
|
93
95
|
}
|
|
94
96
|
export interface Pairing {
|
|
@@ -100,10 +102,12 @@ export interface ReporterPayload {
|
|
|
100
102
|
cwd: string;
|
|
101
103
|
}
|
|
102
104
|
export type OutputFormat = "pretty" | "json" | "sarif";
|
|
105
|
+
export type SemanticsMode = "off" | "input" | "output" | "both";
|
|
103
106
|
export interface CheckOptions {
|
|
104
107
|
cwd: string;
|
|
105
108
|
pattern: string;
|
|
106
109
|
format: OutputFormat;
|
|
110
|
+
semantics: SemanticsMode;
|
|
107
111
|
maxIssues?: number;
|
|
108
112
|
changedOnly: boolean;
|
|
109
113
|
}
|
package/dist/reporters/json.js
CHANGED
|
@@ -3,6 +3,8 @@ export function renderJson(payload) {
|
|
|
3
3
|
summary: {
|
|
4
4
|
checkedPairs: payload.result.checkedPairs,
|
|
5
5
|
totalIssues: payload.result.totalIssues,
|
|
6
|
+
semanticsMode: payload.result.semanticsMode,
|
|
7
|
+
semanticIssueCount: payload.result.semanticIssueCount,
|
|
6
8
|
unmatchedTypes: payload.result.unmatchedTypes,
|
|
7
9
|
unmatchedSchemas: payload.result.unmatchedSchemas,
|
|
8
10
|
errors: payload.result.errors,
|
package/dist/reporters/pretty.js
CHANGED
|
@@ -31,5 +31,8 @@ export function renderPretty(payload) {
|
|
|
31
31
|
.join(", ")}`);
|
|
32
32
|
}
|
|
33
33
|
lines.push(`Checked pairs: ${payload.result.checkedPairs} | Issues: ${payload.result.totalIssues}`);
|
|
34
|
+
if (payload.result.semanticsMode !== "off") {
|
|
35
|
+
lines.push(`Semantic mode: ${payload.result.semanticsMode} | Semantic issues: ${payload.result.semanticIssueCount}`);
|
|
36
|
+
}
|
|
34
37
|
return lines.join("\n").trim();
|
|
35
38
|
}
|
package/dist/reporters/sarif.js
CHANGED
|
@@ -3,6 +3,7 @@ function levelForIssue(kind) {
|
|
|
3
3
|
switch (kind) {
|
|
4
4
|
case "type_mismatch":
|
|
5
5
|
case "optional_mismatch":
|
|
6
|
+
case "semantic_mismatch":
|
|
6
7
|
return "error";
|
|
7
8
|
default:
|
|
8
9
|
return "warning";
|
|
@@ -18,6 +19,8 @@ function ruleName(kind) {
|
|
|
18
19
|
return "optional-mismatch";
|
|
19
20
|
case "type_mismatch":
|
|
20
21
|
return "type-mismatch";
|
|
22
|
+
case "semantic_mismatch":
|
|
23
|
+
return "semantic-mismatch";
|
|
21
24
|
default:
|
|
22
25
|
return "drift";
|
|
23
26
|
}
|
|
@@ -40,6 +43,10 @@ export function renderSarif(payload) {
|
|
|
40
43
|
id: "type-mismatch",
|
|
41
44
|
shortDescription: { text: "Type mismatch between TS and Zod" },
|
|
42
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "semantic-mismatch",
|
|
48
|
+
shortDescription: { text: "TypeScript semantic mismatch against z.input/z.output" },
|
|
49
|
+
},
|
|
43
50
|
];
|
|
44
51
|
const results = payload.result.pairs.flatMap((pair) => pair.issues.map((issue) => {
|
|
45
52
|
const location = issue.schemaLocation ?? issue.typeLocation;
|
package/package.json
CHANGED