supabase-strict-check 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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/lib/catalog.d.ts +5 -0
- package/dist/lib/catalog.js +293 -0
- package/dist/lib/checker.d.ts +70 -0
- package/dist/lib/checker.js +791 -0
- package/dist/lib/program.d.ts +6 -0
- package/dist/lib/program.js +145 -0
- package/dist/lib/scope.d.ts +12 -0
- package/dist/lib/scope.js +30 -0
- package/dist/lib/select.d.ts +6 -0
- package/dist/lib/select.js +127 -0
- package/dist/lib/types.d.ts +54 -0
- package/dist/lib/types.js +6 -0
- package/dist/run.d.ts +1 -0
- package/dist/run.js +71 -0
- package/dist/utils/ast.d.ts +50 -0
- package/dist/utils/ast.js +335 -0
- package/dist/utils/clients.d.ts +14 -0
- package/dist/utils/clients.js +135 -0
- package/dist/utils/files.d.ts +6 -0
- package/dist/utils/files.js +144 -0
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.js +64 -0
- package/package.json +29 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export declare function createBackendProgram(target: string, files: string[], typesFile: string): ts.Program;
|
|
3
|
+
export declare function stringLiteralsFromType(type: ts.Type): string[] | null;
|
|
4
|
+
export declare function stringFromType(checker: ts.TypeChecker, node: ts.Node): string | null;
|
|
5
|
+
export declare function propertyNamesFromType(checker: ts.TypeChecker, node: ts.Node): string[] | null;
|
|
6
|
+
export declare function calleeFunction(call: ts.CallExpression, checker: ts.TypeChecker): ts.FunctionLikeDeclaration | undefined;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createBackendProgram = createBackendProgram;
|
|
7
|
+
exports.stringLiteralsFromType = stringLiteralsFromType;
|
|
8
|
+
exports.stringFromType = stringFromType;
|
|
9
|
+
exports.propertyNamesFromType = propertyNamesFromType;
|
|
10
|
+
exports.calleeFunction = calleeFunction;
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
13
|
+
function createBackendProgram(target, files, typesFile) {
|
|
14
|
+
const configPath = typescript_1.default.findConfigFile(target, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
15
|
+
const rootNames = [...new Set([...files, typesFile])];
|
|
16
|
+
if (!configPath) {
|
|
17
|
+
return typescript_1.default.createProgram({
|
|
18
|
+
rootNames,
|
|
19
|
+
options: {
|
|
20
|
+
noEmit: true,
|
|
21
|
+
skipLibCheck: true,
|
|
22
|
+
strict: true,
|
|
23
|
+
target: typescript_1.default.ScriptTarget.ES2022,
|
|
24
|
+
module: typescript_1.default.ModuleKind.CommonJS,
|
|
25
|
+
moduleResolution: typescript_1.default.ModuleResolutionKind.Node10,
|
|
26
|
+
esModuleInterop: true,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const { config, error } = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
31
|
+
if (error) {
|
|
32
|
+
throw new Error(typescript_1.default.flattenDiagnosticMessageText(error.messageText, "\n"));
|
|
33
|
+
}
|
|
34
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(config, typescript_1.default.sys, node_path_1.default.dirname(configPath));
|
|
35
|
+
return typescript_1.default.createProgram({
|
|
36
|
+
rootNames: [...new Set([...parsed.fileNames, ...rootNames])],
|
|
37
|
+
options: {
|
|
38
|
+
...parsed.options,
|
|
39
|
+
noEmit: true,
|
|
40
|
+
skipLibCheck: true,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function stringLiteralsFromType(type) {
|
|
45
|
+
if (type.isStringLiteral())
|
|
46
|
+
return [type.value];
|
|
47
|
+
if (type.isUnion()) {
|
|
48
|
+
const lits = [];
|
|
49
|
+
for (const t of type.types) {
|
|
50
|
+
if (!t.isStringLiteral())
|
|
51
|
+
return null;
|
|
52
|
+
lits.push(t.value);
|
|
53
|
+
}
|
|
54
|
+
return lits.length ? lits : null;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function stringFromType(checker, node) {
|
|
59
|
+
const lits = stringLiteralsFromType(checker.getTypeAtLocation(node));
|
|
60
|
+
return lits?.length === 1 ? lits[0] : null;
|
|
61
|
+
}
|
|
62
|
+
const OBJECT_PROTO = new Set([
|
|
63
|
+
"constructor",
|
|
64
|
+
"toString",
|
|
65
|
+
"toLocaleString",
|
|
66
|
+
"valueOf",
|
|
67
|
+
"hasOwnProperty",
|
|
68
|
+
"isPrototypeOf",
|
|
69
|
+
"propertyIsEnumerable",
|
|
70
|
+
]);
|
|
71
|
+
function propertyNamesFromType(checker, node) {
|
|
72
|
+
const type = checker.getTypeAtLocation(node);
|
|
73
|
+
const flags = type.getFlags();
|
|
74
|
+
if (flags & (typescript_1.default.TypeFlags.Any | typescript_1.default.TypeFlags.Unknown | typescript_1.default.TypeFlags.Never))
|
|
75
|
+
return null;
|
|
76
|
+
if (type.getStringIndexType() && type.getProperties().length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
const names = type.getProperties()
|
|
79
|
+
.map((p) => p.getName())
|
|
80
|
+
.filter((n) => n && !n.startsWith("__") && !OBJECT_PROTO.has(n));
|
|
81
|
+
return names.length ? names : null;
|
|
82
|
+
}
|
|
83
|
+
function calleeFunction(call, checker) {
|
|
84
|
+
const expr = call.expression;
|
|
85
|
+
const target = typescript_1.default.isPropertyAccessExpression(expr) ? expr.name : expr;
|
|
86
|
+
const symbol = checker.getSymbolAtLocation(target) ?? checker.getSymbolAtLocation(expr);
|
|
87
|
+
if (!symbol)
|
|
88
|
+
return undefined;
|
|
89
|
+
return functionFromSymbol(symbol, checker, new Set());
|
|
90
|
+
}
|
|
91
|
+
function functionFromSymbol(symbol, checker, seen) {
|
|
92
|
+
if (seen.has(symbol))
|
|
93
|
+
return undefined;
|
|
94
|
+
seen.add(symbol);
|
|
95
|
+
let resolved = symbol;
|
|
96
|
+
if (resolved.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
97
|
+
resolved = checker.getAliasedSymbol(resolved);
|
|
98
|
+
}
|
|
99
|
+
for (const decl of resolved.getDeclarations() ?? []) {
|
|
100
|
+
if (typescript_1.default.isFunctionDeclaration(decl)
|
|
101
|
+
|| typescript_1.default.isMethodDeclaration(decl)
|
|
102
|
+
|| typescript_1.default.isFunctionExpression(decl)
|
|
103
|
+
|| typescript_1.default.isArrowFunction(decl)) {
|
|
104
|
+
return decl;
|
|
105
|
+
}
|
|
106
|
+
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
107
|
+
const init = unwrapInit(decl.initializer);
|
|
108
|
+
if (typescript_1.default.isFunctionExpression(init) || typescript_1.default.isArrowFunction(init))
|
|
109
|
+
return init;
|
|
110
|
+
}
|
|
111
|
+
if (typescript_1.default.isShorthandPropertyAssignment(decl)) {
|
|
112
|
+
const value = checker.getShorthandAssignmentValueSymbol(decl);
|
|
113
|
+
if (value) {
|
|
114
|
+
const fn = functionFromSymbol(value, checker, seen);
|
|
115
|
+
if (fn)
|
|
116
|
+
return fn;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (typescript_1.default.isPropertyAssignment(decl)) {
|
|
120
|
+
const init = unwrapInit(decl.initializer);
|
|
121
|
+
if (typescript_1.default.isFunctionExpression(init) || typescript_1.default.isArrowFunction(init))
|
|
122
|
+
return init;
|
|
123
|
+
if (typescript_1.default.isIdentifier(init)) {
|
|
124
|
+
const value = checker.getSymbolAtLocation(init);
|
|
125
|
+
if (value) {
|
|
126
|
+
const fn = functionFromSymbol(value, checker, seen);
|
|
127
|
+
if (fn)
|
|
128
|
+
return fn;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
function unwrapInit(expr) {
|
|
136
|
+
let current = expr;
|
|
137
|
+
while (typescript_1.default.isParenthesizedExpression(current)
|
|
138
|
+
|| typescript_1.default.isAsExpression(current)
|
|
139
|
+
|| typescript_1.default.isTypeAssertionExpression(current)
|
|
140
|
+
|| typescript_1.default.isSatisfiesExpression(current)
|
|
141
|
+
|| typescript_1.default.isNonNullExpression(current)) {
|
|
142
|
+
current = current.expression;
|
|
143
|
+
}
|
|
144
|
+
return current;
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { QueryBinding } from "./types";
|
|
2
|
+
export declare class Scope {
|
|
3
|
+
private readonly parent?;
|
|
4
|
+
private readonly queries;
|
|
5
|
+
private readonly objects;
|
|
6
|
+
constructor(parent?: Scope | undefined);
|
|
7
|
+
child(): Scope;
|
|
8
|
+
setQuery(name: string, binding: QueryBinding): void;
|
|
9
|
+
getQuery(name: string): QueryBinding | undefined;
|
|
10
|
+
setObject(name: string, expr: import("typescript").Expression): void;
|
|
11
|
+
getObject(name: string): import("typescript").Expression | undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scope = void 0;
|
|
4
|
+
class Scope {
|
|
5
|
+
parent;
|
|
6
|
+
queries = new Map();
|
|
7
|
+
objects = new Map();
|
|
8
|
+
constructor(parent) {
|
|
9
|
+
this.parent = parent;
|
|
10
|
+
}
|
|
11
|
+
child() {
|
|
12
|
+
return new Scope(this);
|
|
13
|
+
}
|
|
14
|
+
setQuery(name, binding) {
|
|
15
|
+
this.queries.set(name, {
|
|
16
|
+
relation: binding.relation,
|
|
17
|
+
embeds: new Map(binding.embeds),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
getQuery(name) {
|
|
21
|
+
return this.queries.get(name) ?? this.parent?.getQuery(name);
|
|
22
|
+
}
|
|
23
|
+
setObject(name, expr) {
|
|
24
|
+
this.objects.set(name, expr);
|
|
25
|
+
}
|
|
26
|
+
getObject(name) {
|
|
27
|
+
return this.objects.get(name) ?? this.parent?.getObject(name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.Scope = Scope;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SelectItem } from "./types";
|
|
2
|
+
export declare function splitTopLevel(input: string, sep: string): string[];
|
|
3
|
+
export declare function parseSelectList(input: string): SelectItem[];
|
|
4
|
+
export declare function parseSelectItem(raw: string): SelectItem | null;
|
|
5
|
+
/** Columns referenced in PostgREST `or` / `and` filter strings. */
|
|
6
|
+
export declare function filterColumns(filter: string): string[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitTopLevel = splitTopLevel;
|
|
4
|
+
exports.parseSelectList = parseSelectList;
|
|
5
|
+
exports.parseSelectItem = parseSelectItem;
|
|
6
|
+
exports.filterColumns = filterColumns;
|
|
7
|
+
function splitTopLevel(input, sep) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
let depth = 0;
|
|
10
|
+
let inQuote = null;
|
|
11
|
+
let start = 0;
|
|
12
|
+
for (let i = 0; i < input.length; i++) {
|
|
13
|
+
const ch = input[i];
|
|
14
|
+
if (inQuote) {
|
|
15
|
+
if (ch === inQuote && input[i - 1] !== "\\")
|
|
16
|
+
inQuote = null;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (ch === "'" || ch === '"') {
|
|
20
|
+
inQuote = ch;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (ch === "(")
|
|
24
|
+
depth++;
|
|
25
|
+
else if (ch === ")")
|
|
26
|
+
depth--;
|
|
27
|
+
else if (ch === sep && depth === 0) {
|
|
28
|
+
parts.push(input.slice(start, i));
|
|
29
|
+
start = i + sep.length;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
parts.push(input.slice(start));
|
|
33
|
+
return parts;
|
|
34
|
+
}
|
|
35
|
+
function parseSelectList(input) {
|
|
36
|
+
const trimmed = input.trim();
|
|
37
|
+
if (!trimmed)
|
|
38
|
+
return [];
|
|
39
|
+
const items = [];
|
|
40
|
+
for (const part of splitTopLevel(trimmed, ",")) {
|
|
41
|
+
const item = parseSelectItem(part.trim());
|
|
42
|
+
if (item)
|
|
43
|
+
items.push(item);
|
|
44
|
+
}
|
|
45
|
+
return items;
|
|
46
|
+
}
|
|
47
|
+
function parseSelectItem(raw) {
|
|
48
|
+
let s = raw.trim();
|
|
49
|
+
if (!s)
|
|
50
|
+
return null;
|
|
51
|
+
let children = null;
|
|
52
|
+
if (s.endsWith(")")) {
|
|
53
|
+
let depth = 0;
|
|
54
|
+
let open = -1;
|
|
55
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
56
|
+
if (s[i] === ")")
|
|
57
|
+
depth++;
|
|
58
|
+
else if (s[i] === "(") {
|
|
59
|
+
depth--;
|
|
60
|
+
if (depth === 0) {
|
|
61
|
+
open = i;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (open >= 0) {
|
|
67
|
+
children = parseSelectList(s.slice(open + 1, s.length - 1));
|
|
68
|
+
s = s.slice(0, open).trim();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let alias = null;
|
|
72
|
+
const colon = s.indexOf(":");
|
|
73
|
+
if (colon > 0) {
|
|
74
|
+
const maybeAlias = s.slice(0, colon).trim();
|
|
75
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(maybeAlias) && !maybeAlias.includes("!")) {
|
|
76
|
+
alias = maybeAlias;
|
|
77
|
+
s = s.slice(colon + 1).trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const bangParts = s.split("!").map((p) => p.trim()).filter(Boolean);
|
|
81
|
+
if (bangParts.length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
let name = bangParts[0];
|
|
84
|
+
const castAt = name.indexOf("::");
|
|
85
|
+
if (castAt >= 0)
|
|
86
|
+
name = name.slice(0, castAt).trim();
|
|
87
|
+
if (name === "")
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
alias,
|
|
91
|
+
name,
|
|
92
|
+
hints: bangParts.slice(1).map((h) => h.replace(/::.*$/, "")),
|
|
93
|
+
children,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const FILTER_OP = /^(eq|neq|gt|gte|lt|lte|like|ilike|is|in|cs|cd|ov|fts|not|match)$/i;
|
|
97
|
+
/** Columns referenced in PostgREST `or` / `and` filter strings. */
|
|
98
|
+
function filterColumns(filter) {
|
|
99
|
+
const cols = [];
|
|
100
|
+
const walk = (s) => {
|
|
101
|
+
const trimmed = s.trim();
|
|
102
|
+
if (!trimmed)
|
|
103
|
+
return;
|
|
104
|
+
const grouped = trimmed.match(/^(and|or)\((.*)\)$/i);
|
|
105
|
+
if (grouped) {
|
|
106
|
+
for (const part of splitTopLevel(grouped[2], ","))
|
|
107
|
+
walk(part);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const notWrap = trimmed.match(/^not\((.*)\)$/i);
|
|
111
|
+
if (notWrap) {
|
|
112
|
+
walk(notWrap[1]);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_.]*)\.(eq|neq|gt|gte|lt|lte|like|ilike|is|in|cs|cd|ov|fts|not|match)\b/i);
|
|
116
|
+
if (match && FILTER_OP.test(match[2])) {
|
|
117
|
+
cols.push(match[1]);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const re = /([A-Za-z_][A-Za-z0-9_.]*)\.(eq|neq|gt|gte|lt|lte|like|ilike|is|in|cs|cd|ov|fts|not|match)\b/gi;
|
|
121
|
+
let m;
|
|
122
|
+
while ((m = re.exec(trimmed)))
|
|
123
|
+
cols.push(m[1]);
|
|
124
|
+
};
|
|
125
|
+
walk(filter);
|
|
126
|
+
return cols;
|
|
127
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface Relationship {
|
|
2
|
+
foreignKeyName: string;
|
|
3
|
+
columns: string[];
|
|
4
|
+
isOneToOne: boolean;
|
|
5
|
+
referencedRelation: string;
|
|
6
|
+
referencedColumns: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface RpcFunction {
|
|
9
|
+
name: string;
|
|
10
|
+
argNames: Set<string>;
|
|
11
|
+
requiredArgs: Set<string>;
|
|
12
|
+
argsNever: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface Relation {
|
|
15
|
+
schema: string;
|
|
16
|
+
name: string;
|
|
17
|
+
kind: "table" | "view";
|
|
18
|
+
columns: Set<string>;
|
|
19
|
+
insertColumns: Set<string>;
|
|
20
|
+
insertRequired: Set<string>;
|
|
21
|
+
updateColumns: Set<string>;
|
|
22
|
+
relationships: Relationship[];
|
|
23
|
+
/** string-literal domains for a column (enums / unions), if known */
|
|
24
|
+
valueDomain: Map<string, string[]>;
|
|
25
|
+
}
|
|
26
|
+
export interface Catalog {
|
|
27
|
+
schemas: Set<string>;
|
|
28
|
+
relations: Map<string, Relation>;
|
|
29
|
+
byName: Map<string, Relation[]>;
|
|
30
|
+
functions: Map<string, Map<string, RpcFunction>>;
|
|
31
|
+
enums: Map<string, Map<string, string[]>>;
|
|
32
|
+
}
|
|
33
|
+
export interface SelectItem {
|
|
34
|
+
alias: string | null;
|
|
35
|
+
name: string;
|
|
36
|
+
hints: string[];
|
|
37
|
+
children: SelectItem[] | null;
|
|
38
|
+
}
|
|
39
|
+
export interface QueryError {
|
|
40
|
+
file: string;
|
|
41
|
+
line: number;
|
|
42
|
+
column: number;
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
export interface MethodCall {
|
|
46
|
+
name: string;
|
|
47
|
+
args: import("typescript").Expression[];
|
|
48
|
+
node: import("typescript").CallExpression;
|
|
49
|
+
}
|
|
50
|
+
export interface QueryBinding {
|
|
51
|
+
relation: Relation;
|
|
52
|
+
embeds: Map<string, Relation>;
|
|
53
|
+
}
|
|
54
|
+
export declare function relKey(schema: string, name: string): string;
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(): Promise<void>;
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.run = run;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const cli_1 = require("./cli");
|
|
10
|
+
const catalog_1 = require("./lib/catalog");
|
|
11
|
+
const checker_1 = require("./lib/checker");
|
|
12
|
+
const program_1 = require("./lib/program");
|
|
13
|
+
const clients_1 = require("./utils/clients");
|
|
14
|
+
const files_1 = require("./utils/files");
|
|
15
|
+
function formatIssue(issue, kind) {
|
|
16
|
+
return `${kind} ${issue.file}:${issue.line}:${issue.column} ${issue.message}`;
|
|
17
|
+
}
|
|
18
|
+
async function run() {
|
|
19
|
+
const { cwd, target, types } = await (0, cli_1.resolveCliPaths)();
|
|
20
|
+
const catalog = (0, catalog_1.loadCatalog)(types);
|
|
21
|
+
const files = (0, files_1.collectSourceFiles)(target, types);
|
|
22
|
+
const srcDir = node_fs_1.default.existsSync(node_path_1.default.join(target, "src")) ? node_path_1.default.join(target, "src") : target;
|
|
23
|
+
const program = (0, program_1.createBackendProgram)(target, files, types);
|
|
24
|
+
const tsChecker = program.getTypeChecker();
|
|
25
|
+
const clientNames = (0, clients_1.collectClientNames)(program, tsChecker);
|
|
26
|
+
const imported = (0, files_1.loadImportedConsts)(files, srcDir);
|
|
27
|
+
const errors = [];
|
|
28
|
+
const warnings = [];
|
|
29
|
+
const instantiated = new Set();
|
|
30
|
+
const pendingAny = [];
|
|
31
|
+
const makeChecker = (sf) => new checker_1.Checker({
|
|
32
|
+
catalog,
|
|
33
|
+
sf,
|
|
34
|
+
consts: (0, files_1.fileConsts)(sf, imported),
|
|
35
|
+
tsChecker,
|
|
36
|
+
clientNames,
|
|
37
|
+
instantiated,
|
|
38
|
+
pendingAny,
|
|
39
|
+
errors,
|
|
40
|
+
warnings,
|
|
41
|
+
});
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const sf = program.getSourceFile(file);
|
|
44
|
+
if (!sf)
|
|
45
|
+
continue;
|
|
46
|
+
makeChecker(sf).checkFile();
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < 20; i++) {
|
|
49
|
+
const before = pendingAny.length;
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const sf = program.getSourceFile(file);
|
|
52
|
+
if (!sf)
|
|
53
|
+
continue;
|
|
54
|
+
makeChecker(sf).followAnyPayloads();
|
|
55
|
+
}
|
|
56
|
+
if (pendingAny.length === before)
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
(0, checker_1.flushUnhitAnyPayloads)(pendingAny, errors);
|
|
60
|
+
for (const warning of warnings)
|
|
61
|
+
console.warn(formatIssue(warning, "warning"));
|
|
62
|
+
for (const error of errors)
|
|
63
|
+
console.error(formatIssue(error, "error"));
|
|
64
|
+
const summary = `checked ${files.length} files in ${node_path_1.default.relative(cwd, target) || "."} against ${catalog.relations.size} relations`;
|
|
65
|
+
if (errors.length === 0) {
|
|
66
|
+
console.log(`${summary} — ok`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.error(`\n${summary} — ${errors.length} error(s)`);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { MethodCall } from "../lib/types";
|
|
3
|
+
export declare function loc(sf: ts.SourceFile, node: ts.Node): {
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function propName(name: ts.PropertyName): string | null;
|
|
8
|
+
export declare function unwrapExpr(expr: ts.Expression): ts.Expression;
|
|
9
|
+
export declare function evalString(expr: ts.Expression, consts: Map<string, string>, opts?: {
|
|
10
|
+
checker?: ts.TypeChecker;
|
|
11
|
+
subst?: Map<string, string>;
|
|
12
|
+
}): string | null;
|
|
13
|
+
/** All string-literal possibilities (unions / `a ? "id" : "slug"`). */
|
|
14
|
+
export declare function evalStrings(expr: ts.Expression, consts: Map<string, string>, opts?: {
|
|
15
|
+
checker?: ts.TypeChecker;
|
|
16
|
+
subst?: Map<string, string>;
|
|
17
|
+
}): string[] | null;
|
|
18
|
+
export declare function stringish(expr: ts.Expression, consts: Map<string, string>, opts?: {
|
|
19
|
+
checker?: ts.TypeChecker;
|
|
20
|
+
subst?: Map<string, string>;
|
|
21
|
+
}): {
|
|
22
|
+
text: string;
|
|
23
|
+
complete: boolean;
|
|
24
|
+
} | null;
|
|
25
|
+
export declare function bindingHasIdent(name: ts.BindingName, ident: string): boolean;
|
|
26
|
+
export declare function enclosingParam(ident: ts.Identifier): {
|
|
27
|
+
fn: ts.FunctionLikeDeclaration;
|
|
28
|
+
index: number;
|
|
29
|
+
} | null;
|
|
30
|
+
export declare function isParameterIdentifier(ident: ts.Identifier): boolean;
|
|
31
|
+
export declare function callReceiver(call: ts.CallExpression): ts.Expression | undefined;
|
|
32
|
+
export declare function isArrayFrom(fromCall: MethodCall): boolean;
|
|
33
|
+
export declare function chainProps(expr: ts.Expression): string[];
|
|
34
|
+
export declare function collectChain(tail: ts.CallExpression): MethodCall[];
|
|
35
|
+
export declare function chainRoot(tail: ts.CallExpression): ts.Expression | undefined;
|
|
36
|
+
export declare function isChainTail(node: ts.CallExpression): boolean;
|
|
37
|
+
export declare function assignedName(tail: ts.CallExpression): string | null;
|
|
38
|
+
export declare function objectLiteral(expr: ts.Expression | undefined): ts.ObjectLiteralExpression | null;
|
|
39
|
+
export declare function objectKeys(expr: ts.Expression | undefined): {
|
|
40
|
+
keys: string[];
|
|
41
|
+
hasSpread: boolean;
|
|
42
|
+
} | null;
|
|
43
|
+
export declare function optionString(args: ts.Expression[], options: string[], consts: Map<string, string>, opts?: {
|
|
44
|
+
checker?: ts.TypeChecker;
|
|
45
|
+
subst?: Map<string, string>;
|
|
46
|
+
}): string | null;
|
|
47
|
+
export declare function literalValue(expr: ts.Expression, consts: Map<string, string>, opts?: {
|
|
48
|
+
checker?: ts.TypeChecker;
|
|
49
|
+
subst?: Map<string, string>;
|
|
50
|
+
}): string | number | boolean | null | undefined;
|