bk-schema-checker 0.1.0 → 0.1.1
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 +1 -1
- package/package.json +1 -1
- package/src/ts.js +117 -6
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
请求参数不看 mock 读了什么,而看 services 调用的参数类型:GET 的第二个参数是查询参数,POST、PUT、PATCH、DELETE 的第二个参数是请求体。路径参数来自 Django 的 `<str:system_id>` 这类转换器。
|
|
16
16
|
|
|
17
|
-
后台用 Python 标准库 `ast` 静态读取,包括字段继承、自定义字段子类、`SerializerMethodField` 的返回注解,以及能静态求值的 `choices`。前端 `tsconfig.json` 的 `paths` 用来展开 `@common/const` 这类别名;`export enum`、`export interface`
|
|
17
|
+
后台用 Python 标准库 `ast` 静态读取,包括字段继承、自定义字段子类、`SerializerMethodField` 的返回注解,以及能静态求值的 `choices`。前端 `tsconfig.json` 的 `paths` 用来展开 `@common/const` 这类别名;`export enum`、`export interface`(含 `extends`)、`A & { extra }` 交叉类型和 `` `${Enum}` `` 会展开成具体类型再比较。
|
|
18
18
|
|
|
19
19
|
## 环境要求
|
|
20
20
|
|
package/package.json
CHANGED
package/src/ts.js
CHANGED
|
@@ -480,7 +480,10 @@ function parseTypeExport(source, cursor, mod) {
|
|
|
480
480
|
if (interfaceLike || source[bodyAt] === "{") {
|
|
481
481
|
const open = source.indexOf("{", bodyAt);
|
|
482
482
|
const block = sliceBalanced(source, open);
|
|
483
|
-
|
|
483
|
+
const body = source.slice(open, block.end + 1);
|
|
484
|
+
const parents = interfaceLike ? parseInterfaceExtends(source, bodyAt, open) : [];
|
|
485
|
+
const composed = parents.length ? `${parents.join(" & ")} & ${body}` : body;
|
|
486
|
+
mod.types.set(ident.name, { kind: "source", source: composed });
|
|
484
487
|
return block.end + 1;
|
|
485
488
|
}
|
|
486
489
|
if (source[bodyAt] === "=") {
|
|
@@ -492,6 +495,31 @@ function parseTypeExport(source, cursor, mod) {
|
|
|
492
495
|
return skipStatement(source, cursor).end;
|
|
493
496
|
}
|
|
494
497
|
|
|
498
|
+
function parseInterfaceExtends(source, start, open) {
|
|
499
|
+
const text = source.slice(start, open).trim();
|
|
500
|
+
if (!/^extends\b/.test(text)) {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
const names = [];
|
|
504
|
+
let cursor = skipSpace(text, "extends".length);
|
|
505
|
+
while (cursor < text.length) {
|
|
506
|
+
const ident = readIdentifier(text, cursor);
|
|
507
|
+
if (!ident) {
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
names.push(ident.name);
|
|
511
|
+
cursor = skipSpace(text, ident.end);
|
|
512
|
+
if (text[cursor] === "<") {
|
|
513
|
+
const generic = sliceAngles(text, cursor);
|
|
514
|
+
cursor = skipSpace(text, generic ? generic.end + 1 : cursor + 1);
|
|
515
|
+
}
|
|
516
|
+
if (text[cursor] === ",") {
|
|
517
|
+
cursor = skipSpace(text, cursor + 1);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return names;
|
|
521
|
+
}
|
|
522
|
+
|
|
495
523
|
function parseEnumExport(source, cursor, mod) {
|
|
496
524
|
const head = source.slice(cursor, cursor + 24);
|
|
497
525
|
const keyword = /^export\s+const\s+enum\b/.test(head) ? "export const enum" : "export enum";
|
|
@@ -1747,6 +1775,17 @@ function skeleton(type) {
|
|
|
1747
1775
|
value.fields = Object.fromEntries(type.props.map((prop) => [prop.name, skeleton(prop.type)]));
|
|
1748
1776
|
return value;
|
|
1749
1777
|
}
|
|
1778
|
+
if (type.kind === "intersection") {
|
|
1779
|
+
const value = new Partial("object");
|
|
1780
|
+
value.fields = {};
|
|
1781
|
+
for (const item of type.types) {
|
|
1782
|
+
const next = skeleton(item);
|
|
1783
|
+
if (next instanceof Partial && next.fields) {
|
|
1784
|
+
Object.assign(value.fields, next.fields);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return value;
|
|
1788
|
+
}
|
|
1750
1789
|
return new Partial();
|
|
1751
1790
|
}
|
|
1752
1791
|
|
|
@@ -1764,7 +1803,7 @@ function parseTypeAt(source, cursor) {
|
|
|
1764
1803
|
}
|
|
1765
1804
|
|
|
1766
1805
|
function parseUnionType(source, cursor) {
|
|
1767
|
-
let current =
|
|
1806
|
+
let current = parseIntersectionType(source, cursor);
|
|
1768
1807
|
const types = [current.type];
|
|
1769
1808
|
let end = current.end;
|
|
1770
1809
|
while (true) {
|
|
@@ -1772,13 +1811,29 @@ function parseUnionType(source, cursor) {
|
|
|
1772
1811
|
if (source[next] !== "|") {
|
|
1773
1812
|
break;
|
|
1774
1813
|
}
|
|
1775
|
-
current =
|
|
1814
|
+
current = parseIntersectionType(source, next + 1);
|
|
1776
1815
|
types.push(current.type);
|
|
1777
1816
|
end = current.end;
|
|
1778
1817
|
}
|
|
1779
1818
|
return { type: types.length === 1 ? types[0] : { kind: "union", types }, end };
|
|
1780
1819
|
}
|
|
1781
1820
|
|
|
1821
|
+
function parseIntersectionType(source, cursor) {
|
|
1822
|
+
let current = parsePrimaryType(source, cursor);
|
|
1823
|
+
const types = [current.type];
|
|
1824
|
+
let end = current.end;
|
|
1825
|
+
while (true) {
|
|
1826
|
+
const next = skipSpace(source, end);
|
|
1827
|
+
if (source[next] !== "&") {
|
|
1828
|
+
break;
|
|
1829
|
+
}
|
|
1830
|
+
current = parsePrimaryType(source, next + 1);
|
|
1831
|
+
types.push(current.type);
|
|
1832
|
+
end = current.end;
|
|
1833
|
+
}
|
|
1834
|
+
return { type: types.length === 1 ? types[0] : { kind: "intersection", types }, end };
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1782
1837
|
function parsePrimaryType(source, cursor) {
|
|
1783
1838
|
const start = skipSpace(source, cursor);
|
|
1784
1839
|
if (source[start] === "{") {
|
|
@@ -2004,16 +2059,19 @@ function splitTypeProps(source) {
|
|
|
2004
2059
|
export function resolveTsType(type, mod, index, seen = new Set()) {
|
|
2005
2060
|
if (!type || type.kind !== "ref" && type.kind !== "index" && type.kind !== "parameters") {
|
|
2006
2061
|
if (type?.kind === "array") {
|
|
2007
|
-
return { kind: "array", item: resolveTsType(type.item, mod, index, seen) };
|
|
2062
|
+
return { kind: "array", item: resolveTsType(type.item, mod, index, new Set(seen)) };
|
|
2008
2063
|
}
|
|
2009
2064
|
if (type?.kind === "union") {
|
|
2010
|
-
return { kind: "union", types: type.types.map((item) => resolveTsType(item, mod, index, seen)) };
|
|
2065
|
+
return { kind: "union", types: type.types.map((item) => resolveTsType(item, mod, index, new Set(seen))) };
|
|
2066
|
+
}
|
|
2067
|
+
if (type?.kind === "intersection") {
|
|
2068
|
+
return mergeResolvedTypes(type.types.map((item) => resolveTsType(item, mod, index, new Set(seen))));
|
|
2011
2069
|
}
|
|
2012
2070
|
if (type?.kind === "object") {
|
|
2013
2071
|
return {
|
|
2014
2072
|
kind: "object",
|
|
2015
2073
|
open: Boolean(type.open),
|
|
2016
|
-
props: type.props.map((prop) => ({ ...prop, type: resolveTsType(prop.type, mod, index, seen) })),
|
|
2074
|
+
props: type.props.map((prop) => ({ ...prop, type: resolveTsType(prop.type, mod, index, new Set(seen)) })),
|
|
2017
2075
|
};
|
|
2018
2076
|
}
|
|
2019
2077
|
return type || { kind: "any" };
|
|
@@ -2044,6 +2102,59 @@ export function resolveTsType(type, mod, index, seen = new Set()) {
|
|
|
2044
2102
|
return resolved;
|
|
2045
2103
|
}
|
|
2046
2104
|
|
|
2105
|
+
function mergeResolvedTypes(types) {
|
|
2106
|
+
const parts = types.flatMap((type) => (type?.kind === "intersection" ? type.types : [type])).filter(Boolean);
|
|
2107
|
+
const unions = parts.filter((type) => type.kind === "union");
|
|
2108
|
+
if (unions.length) {
|
|
2109
|
+
const rest = parts.filter((type) => type.kind !== "union");
|
|
2110
|
+
return {
|
|
2111
|
+
kind: "union",
|
|
2112
|
+
types: unions[0].types.map((item) => mergeResolvedTypes([item, ...unions.slice(1), ...rest])),
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
const objects = parts.filter((type) => type.kind === "object");
|
|
2116
|
+
if (objects.length) {
|
|
2117
|
+
return mergeObjectTypes(objects);
|
|
2118
|
+
}
|
|
2119
|
+
const concrete = parts.filter((type) => type.kind !== "any" && type.kind !== "null");
|
|
2120
|
+
return concrete[0] || parts[0] || { kind: "any" };
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
function mergeObjectTypes(objects) {
|
|
2124
|
+
const props = [];
|
|
2125
|
+
let open = false;
|
|
2126
|
+
for (const object of objects) {
|
|
2127
|
+
open = open || Boolean(object.open);
|
|
2128
|
+
for (const prop of object.props || []) {
|
|
2129
|
+
const index = props.findIndex((item) => item.name === prop.name);
|
|
2130
|
+
if (index === -1) {
|
|
2131
|
+
props.push({ ...prop });
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
const prev = props[index];
|
|
2135
|
+
props[index] = {
|
|
2136
|
+
name: prop.name,
|
|
2137
|
+
optional: Boolean(prev.optional && prop.optional),
|
|
2138
|
+
type: mergePropertyType(prev.type, prop.type),
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
return { kind: "object", props, open };
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function mergePropertyType(left, right) {
|
|
2146
|
+
if (!left || left.kind === "any") {
|
|
2147
|
+
return right;
|
|
2148
|
+
}
|
|
2149
|
+
if (!right || right.kind === "any") {
|
|
2150
|
+
return left;
|
|
2151
|
+
}
|
|
2152
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
2153
|
+
return mergeObjectTypes([left, right]);
|
|
2154
|
+
}
|
|
2155
|
+
return right;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2047
2158
|
function lookupType(mod, name, index, stack = new Set()) {
|
|
2048
2159
|
if (!mod || stack.has(mod.file)) {
|
|
2049
2160
|
return null;
|