@mirascript/typed 0.1.62 → 0.1.64

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/dist/type.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["..\\src\\type.peggy"],"names":["Start","Type","UnionType","PostfixType","PrimaryType","RecordType","FieldList","Field","FieldName","String","Identifier","_","$initializer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAuBgB,aAAW,G;yBAOP;AACpB;AACI,G;gCAGuD;AAC3D;AACA;AACA;AACA;AACI,G;gCAC+C;AACnD;AACA;AACA;AACA;AACI,G;iCAQmC;AACvC;AACA;AACA;AACA;AACI,G;kCAOiD;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACI,G;wBACY;AAChB;AACI,G;sBACoB;AACxB;AACI,G;qBAQW,sBAAoB,G;2BACD,0BAAwB,G;4BAGZ;AAC9C;AACI,G;kCAG0C;AAC9C;AACA;AACA;AACA;AACA;AACI,G;uBACQ;AACZ;AACA;AACA;AACI,G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAvFJA,cAAK;;;;;;;;;;;;;;;;;;WAOLC,aAAI;;;;;;;;;;;;;;WAKJC,kBAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmBTC,oBAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAYXC,oBAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAuBXC,mBAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAIVC,kBAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAKTC,cAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAcLC,kBAAS;;;;;;;;;;;WAQTC,eAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAINC,mBAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAGVC,UAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA9HAC;AACDA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA","file":"type.js"}
1
+ {"version":3,"sources":["../src/type.peggy"],"names":["$top_level_initializer","Start","UnionType","PostfixType","PrimaryType","TypeParamList","TypeArgList","FunctionType","NamedFunction","FunctionBody","FunctionParamList","FunctionParam","RecordType","FieldList","Field","FieldName","HexDigit","EscapeSequence","String","DoubleQuotePart","SingleQuotePart","BacktickPart","DoubleQuoteChar","SingleQuoteChar","BacktickChar","Identifier","_","$initializer"],"mappings":"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","file":"type.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirascript/typed",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "author": "CloudPSS",
5
5
  "license": "MIT",
6
6
  "description": "Parse MiraScript type definitions and generate TypeScript types / JSON schemas.",
@@ -23,17 +23,19 @@
23
23
  "./package.json": "./package.json"
24
24
  },
25
25
  "devDependencies": {
26
- "@types/node": "^26.0.0",
26
+ "@types/node": "^26.0.1",
27
27
  "ava": "^8.0.1",
28
- "js-yaml": "^5.1.0",
28
+ "c8": "^11.0.0",
29
+ "js-yaml": "^5.2.0",
29
30
  "peggy": "^5.1.0"
30
31
  },
31
32
  "dependencies": {
32
- "@types/json-schema": "^7.0.15"
33
+ "@types/json-schema": "^7.0.15",
34
+ "@mirascript/constants": "~0.1.64"
33
35
  },
34
36
  "scripts": {
35
37
  "build": "peggy src/type.peggy --multi-output ./dist --dts --format es --source-map && tsc",
36
38
  "clean": "rimraf dist",
37
- "test": "ava --no-worker-threads"
39
+ "test": "c8 ava --no-worker-threads"
38
40
  }
39
41
  }
package/src/json.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { JSONSchema7 } from 'json-schema';
2
- import type { KnownType, LiteralType, NamedType, Type } from './parser.js';
2
+ import { REG_NUMBER } from '@mirascript/constants';
3
+ import type { KnownType, LiteralType, NamedType, TemplateType, Type } from './parser.js';
3
4
 
4
5
  /** Converts a KnownType or NamedType into JSON Schema */
5
6
  function string(type: KnownType | NamedType): JSONSchema7 {
@@ -29,55 +30,77 @@ function string(type: KnownType | NamedType): JSONSchema7 {
29
30
  }
30
31
  }
31
32
 
32
- /** Converts a Type object into JSON Schema */
33
- export function toJSONSchema(type: Type): JSONSchema7 {
34
- if (typeof type === 'string') {
35
- return string(type);
36
- }
37
- if (type.kind === 'array') {
38
- return {
39
- type: 'array',
40
- items: toJSONSchema(type.element),
41
- };
42
- }
43
- if (type.kind === 'union') {
44
- const literals = type.types.filter(isLiteralType);
45
- if (literals.length === type.types.length) {
46
- return literalEnum(literals);
33
+ /** Escapes a literal string for use in a regular expression */
34
+ function escapeRegex(value: string): string {
35
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
36
+ }
37
+
38
+ const RE_ANY = '.*?';
39
+ const RE_NUMBER = REG_NUMBER.source;
40
+ const RE_BOOLEAN = 'true|false';
41
+
42
+ /** Converts a template interpolation part into a regex pattern fragment */
43
+ function templatePartPattern(part: Type, grouping: boolean): string {
44
+ let result: string;
45
+ if (typeof part === 'symbol') {
46
+ result = RE_ANY;
47
+ } else if (typeof part === 'string') {
48
+ switch (part) {
49
+ case 'number':
50
+ result = RE_NUMBER;
51
+ break;
52
+ case 'boolean':
53
+ result = RE_BOOLEAN;
54
+ break;
55
+ case 'nil':
56
+ case 'never':
57
+ result = '';
58
+ break;
59
+ case 'array':
60
+ case 'record':
61
+ case 'extern':
62
+ case 'any':
63
+ case 'unknown':
64
+ case 'string':
65
+ default:
66
+ result = RE_ANY;
67
+ break;
47
68
  }
48
- return {
49
- anyOf: type.types.map(toJSONSchema),
50
- };
51
- }
52
- if (type.kind === 'record') {
53
- if (type.value !== undefined) {
54
- return {
55
- type: 'object',
56
- additionalProperties: toJSONSchema(type.value),
57
- };
69
+ } else if (part.kind === 'literal') {
70
+ if (typeof part.value === 'boolean') {
71
+ result = String(part.value);
72
+ } else {
73
+ result = escapeRegex(part.value);
58
74
  }
59
- const properties: Record<string, JSONSchema7> = {};
60
- const required: string[] = [];
61
- for (const field of type.fields) {
62
- properties[field.name] = toJSONSchema(field.type);
63
- if (!field.optional) {
64
- required.push(field.name);
75
+ } else if (part.kind === 'union') {
76
+ const patterns = new Set(part.types.map((p) => templatePartPattern(p, false)));
77
+ if (patterns.has(RE_ANY)) {
78
+ result = RE_ANY;
79
+ } else {
80
+ const hasEmpty = patterns.delete('');
81
+ result = Array.from(patterns).join('|');
82
+ if (hasEmpty) {
83
+ result = `(${result})?`;
65
84
  }
66
85
  }
67
- const schema: JSONSchema7 = {
68
- type: 'object',
69
- properties,
70
- };
71
- if (required.length > 0) {
72
- schema.required = required;
73
- }
74
- return schema;
75
- }
76
- if (type.kind === 'literal') {
77
- return literal(type);
86
+ } else {
87
+ result = RE_ANY;
78
88
  }
79
- (type) satisfies never;
80
- return {};
89
+ if (!grouping) return result;
90
+ if (result.startsWith('(')) return result;
91
+ return `(${result})`;
92
+ }
93
+
94
+ /** Converts a TemplateType into a JSON Schema pattern */
95
+ function templatePattern(type: TemplateType): string {
96
+ return `^${type.parts
97
+ .map((p) => {
98
+ if (typeof p == 'object' && p.kind === 'literal' && typeof p.value === 'string') {
99
+ return templatePartPattern(p, false);
100
+ }
101
+ return templatePartPattern(p, true);
102
+ })
103
+ .join('')}$`;
81
104
  }
82
105
 
83
106
  /** Converts a LiteralType into JSON Schema */
@@ -103,3 +126,115 @@ function literalEnum(types: LiteralType[]): JSONSchema7 {
103
126
  function isLiteralType(type: Type): type is LiteralType {
104
127
  return typeof type === 'object' && type.kind === 'literal';
105
128
  }
129
+
130
+ /** Options for toJSONSchema */
131
+ export interface ToJSONSchemaOptions {
132
+ /** When true, object schemas allow arbitrary additional properties */
133
+ loose?: boolean;
134
+ }
135
+
136
+ /** Converts a Type object into JSON Schema */
137
+ export function toJSONSchema(type: Type, options?: ToJSONSchemaOptions): JSONSchema7 {
138
+ const loose = options?.loose ?? false;
139
+ if (typeof type === 'symbol') {
140
+ return {};
141
+ }
142
+ if (typeof type === 'string') {
143
+ return string(type);
144
+ }
145
+ if (type.kind === 'array') {
146
+ return {
147
+ type: 'array',
148
+ items: toJSONSchema(type.element, options),
149
+ };
150
+ }
151
+ if (type.kind === 'union') {
152
+ const anyOf = [];
153
+ const literals = [];
154
+ for (const t of type.types) {
155
+ if (isLiteralType(t)) {
156
+ literals.push(t);
157
+ } else {
158
+ anyOf.push(toJSONSchema(t, options));
159
+ }
160
+ }
161
+ if (literals.length > 0) {
162
+ anyOf.push(literalEnum(literals));
163
+ }
164
+ if (anyOf.length === 1) {
165
+ return anyOf[0]!;
166
+ }
167
+ return { anyOf };
168
+ }
169
+ if (type.kind === 'record') {
170
+ if ('fields' in type) {
171
+ const properties: Record<string, JSONSchema7> = {};
172
+ const required: string[] = [];
173
+ for (const field of type.fields) {
174
+ properties[field.name] = toJSONSchema(field.type, options);
175
+ if (!field.optional && !loose) {
176
+ required.push(field.name);
177
+ }
178
+ }
179
+ const schema: JSONSchema7 = {
180
+ type: 'object',
181
+ properties,
182
+ additionalProperties: loose,
183
+ };
184
+ if (required.length > 0) {
185
+ schema.required = required;
186
+ }
187
+ return schema;
188
+ }
189
+
190
+ const valueSchema = toJSONSchema(type.value, options);
191
+ if (type.key == null) {
192
+ return {
193
+ type: 'object',
194
+ additionalProperties: valueSchema,
195
+ };
196
+ }
197
+ if (typeof type.key == 'object') {
198
+ if (isLiteralType(type.key)) {
199
+ const schema: JSONSchema7 = {
200
+ type: 'object',
201
+ properties: { [String(type.key.value)]: valueSchema },
202
+ additionalProperties: loose,
203
+ };
204
+ return schema;
205
+ } else if (type.key.kind === 'union' && type.key.types.every(isLiteralType)) {
206
+ const schema: JSONSchema7 = {
207
+ type: 'object',
208
+ properties: Object.fromEntries(type.key.types.map((t) => [String(t.value), valueSchema])),
209
+ additionalProperties: loose,
210
+ };
211
+ return schema;
212
+ }
213
+ }
214
+ const pattern = templatePartPattern(type.key, false);
215
+ if (pattern === RE_ANY) {
216
+ return {
217
+ type: 'object',
218
+ additionalProperties: valueSchema,
219
+ };
220
+ }
221
+ const schema: JSONSchema7 = {
222
+ type: 'object',
223
+ patternProperties: { [`^${pattern}$`]: valueSchema },
224
+ additionalProperties: loose,
225
+ };
226
+ return schema;
227
+ }
228
+ if (type.kind === 'literal') {
229
+ return literal(type);
230
+ }
231
+ if (type.kind === 'function') {
232
+ return {};
233
+ }
234
+ if (type.kind === 'template') {
235
+ return { type: 'string', pattern: templatePattern(type) };
236
+ }
237
+ /* c8 ignore next 3 */
238
+ (type) satisfies never;
239
+ return {};
240
+ }
package/src/parser.ts CHANGED
@@ -1,7 +1,19 @@
1
1
  import { parse as _parse } from '../dist/type.js';
2
2
 
3
3
  /** MiraScript type */
4
- export type Type = KnownType | NamedType | LiteralType | ArrayType | UnionType | RecordType;
4
+ export type Type =
5
+ | KnownType
6
+ | NamedType
7
+ | GenericType
8
+ | LiteralType
9
+ | TemplateType
10
+ | ArrayType
11
+ | UnionType
12
+ | RecordType
13
+ | FunctionType;
14
+
15
+ /** Generic type parameter reference in MiraScript */
16
+ export type GenericType = symbol;
5
17
 
6
18
  /** Literal type in MiraScript */
7
19
  export interface LiteralType {
@@ -11,18 +23,17 @@ export interface LiteralType {
11
23
  value: string | boolean;
12
24
  }
13
25
 
26
+ /** Template literal type in MiraScript */
27
+ export interface TemplateType {
28
+ /** Tag */
29
+ kind: 'template';
30
+ /** Interpolated parts, strings are represented as {@link LiteralType} objects, other parts are nested {@link Type} objects. */
31
+ parts: Type[];
32
+ }
33
+
14
34
  /** Known type names in MiraScript */
15
35
  export type KnownType =
16
- | 'string'
17
- | 'number'
18
- | 'boolean'
19
- | 'nil'
20
- | 'array'
21
- | 'record'
22
- | 'extern'
23
- | 'any'
24
- | 'unknown'
25
- | 'never';
36
+ 'string' | 'number' | 'boolean' | 'nil' | 'array' | 'record' | 'extern' | 'any' | 'unknown' | 'never';
26
37
 
27
38
  /** Named type in MiraScript */
28
39
  export type NamedType = string & Record<never, never>;
@@ -44,14 +55,21 @@ export interface ArrayType {
44
55
  }
45
56
 
46
57
  /** Record type in MiraScript */
47
- export interface RecordType {
48
- /** Tag */
49
- kind: 'record';
50
- /** Fields in the record */
51
- fields: RecordField[];
52
- /** Value type for record<T> */
53
- value?: Type;
54
- }
58
+ export type RecordType =
59
+ | {
60
+ /** Tag */
61
+ kind: 'record';
62
+ /** Fields in the record */
63
+ fields: RecordField[];
64
+ }
65
+ | {
66
+ /** Tag */
67
+ kind: 'record';
68
+ /** Key type for record<K, V> */
69
+ key?: Type;
70
+ /** Value type for record<T> or record<K, V> */
71
+ value: Type;
72
+ };
55
73
 
56
74
  /** Record field in MiraScript */
57
75
  export interface RecordField {
@@ -63,9 +81,87 @@ export interface RecordField {
63
81
  type: Type;
64
82
  }
65
83
 
84
+ /** Function type in MiraScript */
85
+ export interface FunctionType {
86
+ /** Tag */
87
+ kind: 'function';
88
+ /** Function name (only allowed at the top level) */
89
+ name?: string;
90
+ /** Generic type parameters */
91
+ typeParams?: GenericType[];
92
+ /** Function parameters */
93
+ params: FunctionParameter[];
94
+ /** Return type */
95
+ returns?: Type;
96
+ }
97
+
98
+ /** Function parameter in MiraScript */
99
+ export interface FunctionParameter {
100
+ /** Parameter name */
101
+ name: string;
102
+ /** Parameter type */
103
+ type: Type;
104
+ /** Whether the parameter is a rest parameter */
105
+ spread?: boolean;
106
+ }
107
+
108
+ /**
109
+ * Replaces generic type parameter names in the AST with unique symbols,
110
+ * scoped per function declaration.
111
+ */
112
+ function resolveGenerics(type: Type, scope = new Map<string, GenericType>()): Type {
113
+ /* c8 ignore next 3 */
114
+ if (typeof type == 'symbol') {
115
+ return type;
116
+ }
117
+ if (typeof type == 'string') {
118
+ return scope.get(type) ?? type;
119
+ }
120
+ if (type.kind === 'function') {
121
+ const newScope = new Map(scope);
122
+ for (const sym of type.typeParams ?? []) {
123
+ newScope.set(sym.description!, sym);
124
+ }
125
+ for (const param of type.params) {
126
+ param.type = resolveGenerics(param.type, newScope);
127
+ }
128
+ if (type.returns != null) type.returns = resolveGenerics(type.returns, newScope);
129
+ return type;
130
+ }
131
+ if (type.kind === 'array') {
132
+ type.element = resolveGenerics(type.element, scope);
133
+ return type;
134
+ }
135
+ if (type.kind === 'union') {
136
+ type.types = type.types.map((t) => resolveGenerics(t, scope));
137
+ return type;
138
+ }
139
+ if (type.kind === 'record') {
140
+ if ('fields' in type) {
141
+ for (const field of type.fields) {
142
+ field.type = resolveGenerics(field.type, scope);
143
+ }
144
+ } else {
145
+ if (type.key != null) type.key = resolveGenerics(type.key, scope);
146
+ if (type.value != null) type.value = resolveGenerics(type.value, scope);
147
+ }
148
+ return type;
149
+ }
150
+ if (type.kind === 'literal') {
151
+ return type;
152
+ }
153
+ if (type.kind === 'template') {
154
+ type.parts = type.parts.map((part) => resolveGenerics(part, scope));
155
+ return type;
156
+ }
157
+ /* c8 ignore next 3 */
158
+ (type) satisfies never;
159
+ return type;
160
+ }
161
+
66
162
  /**
67
163
  * Parses a type string into a Type object.
68
164
  */
69
165
  export function parse(type: string): Type {
70
- return _parse(type) as Type;
166
+ return resolveGenerics(_parse(type) as Type);
71
167
  }