skir 0.0.6 → 0.0.8
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/command_line_parser.d.ts +2 -2
- package/dist/command_line_parser.d.ts.map +1 -1
- package/dist/command_line_parser.js +105 -36
- package/dist/command_line_parser.js.map +1 -1
- package/dist/command_line_parser.test.js +166 -39
- package/dist/command_line_parser.test.js.map +1 -1
- package/dist/compiler.js +51 -54
- package/dist/compiler.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -5
- package/dist/config.js.map +1 -1
- package/dist/config_parser.d.ts +6 -0
- package/dist/config_parser.d.ts.map +1 -1
- package/dist/config_parser.js +55 -31
- package/dist/config_parser.js.map +1 -1
- package/dist/config_parser.test.js +53 -20
- package/dist/config_parser.test.js.map +1 -1
- package/dist/error_renderer.d.ts +1 -0
- package/dist/error_renderer.d.ts.map +1 -1
- package/dist/error_renderer.js +6 -3
- package/dist/error_renderer.js.map +1 -1
- package/dist/exit_error.d.ts +8 -0
- package/dist/exit_error.d.ts.map +1 -0
- package/dist/exit_error.js +8 -0
- package/dist/exit_error.js.map +1 -0
- package/dist/io.d.ts +2 -0
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +22 -3
- package/dist/io.js.map +1 -1
- package/dist/module_collector.d.ts.map +1 -1
- package/dist/module_collector.js +12 -7
- package/dist/module_collector.js.map +1 -1
- package/dist/module_set.js +4 -4
- package/dist/parser.js +6 -6
- package/dist/project_initializer.d.ts.map +1 -1
- package/dist/project_initializer.js +97 -15
- package/dist/project_initializer.js.map +1 -1
- package/dist/snapshotter.d.ts +9 -2
- package/dist/snapshotter.d.ts.map +1 -1
- package/dist/snapshotter.js +35 -17
- package/dist/snapshotter.js.map +1 -1
- package/package.json +8 -6
- package/src/command_line_parser.ts +134 -42
- package/src/compiler.ts +65 -60
- package/src/config.ts +4 -6
- package/src/config_parser.ts +66 -32
- package/src/error_renderer.ts +11 -3
- package/src/exit_error.ts +6 -0
- package/src/io.ts +22 -3
- package/src/module_collector.ts +21 -7
- package/src/module_set.ts +4 -4
- package/src/parser.ts +6 -6
- package/src/project_initializer.ts +97 -15
- package/src/snapshotter.ts +49 -30
package/src/compiler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
2
|
+
import * as FileSystem from "fs/promises";
|
|
3
3
|
import { glob } from "glob";
|
|
4
|
-
import * as
|
|
4
|
+
import * as Paths from "path";
|
|
5
5
|
import type { CodeGenerator } from "skir-internal";
|
|
6
6
|
import Watcher from "watcher";
|
|
7
7
|
import { parseCommandLine } from "./command_line_parser.js";
|
|
@@ -14,8 +14,13 @@ import {
|
|
|
14
14
|
renderErrors,
|
|
15
15
|
renderSkirConfigErrors,
|
|
16
16
|
} from "./error_renderer.js";
|
|
17
|
+
import { ExitError } from "./exit_error.js";
|
|
17
18
|
import { formatModule } from "./formatter.js";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
isDirectory,
|
|
21
|
+
REAL_FILE_SYSTEM,
|
|
22
|
+
rewritePathForRendering,
|
|
23
|
+
} from "./io.js";
|
|
19
24
|
import { collectModules } from "./module_collector.js";
|
|
20
25
|
import { ModuleSet } from "./module_set.js";
|
|
21
26
|
import { initializeProject } from "./project_initializer.js";
|
|
@@ -35,14 +40,12 @@ async function makeGeneratorBundle(
|
|
|
35
40
|
): Promise<GeneratorBundle> {
|
|
36
41
|
const generator = await importCodeGenerator(config.mod);
|
|
37
42
|
let skiroutDirs: string[];
|
|
38
|
-
if (config.outDir ===
|
|
39
|
-
skiroutDirs = ["skirout"];
|
|
40
|
-
} else if (typeof config.outDir === "string") {
|
|
43
|
+
if (typeof config.outDir === "string") {
|
|
41
44
|
skiroutDirs = [config.outDir];
|
|
42
45
|
} else {
|
|
43
46
|
skiroutDirs = config.outDir;
|
|
44
47
|
}
|
|
45
|
-
skiroutDirs = skiroutDirs.map((d) =>
|
|
48
|
+
skiroutDirs = skiroutDirs.map((d) => Paths.join(root, d));
|
|
46
49
|
return {
|
|
47
50
|
generator: generator,
|
|
48
51
|
config: config.config,
|
|
@@ -133,7 +136,7 @@ class WatchModeMainLoop {
|
|
|
133
136
|
const successMessage = `Generation succeeded at ${date}`;
|
|
134
137
|
console.log(makeGreen(successMessage));
|
|
135
138
|
console.log("\nWaiting for changes in files matching:");
|
|
136
|
-
const glob =
|
|
139
|
+
const glob = Paths.resolve(Paths.join(this.srcDir, "/**/*.skir"));
|
|
137
140
|
console.log(` ${glob}`);
|
|
138
141
|
}
|
|
139
142
|
return true;
|
|
@@ -150,11 +153,11 @@ class WatchModeMainLoop {
|
|
|
150
153
|
const { skiroutDirs } = this;
|
|
151
154
|
const preExistingAbsolutePaths = new Set<string>();
|
|
152
155
|
for (const skiroutDir of skiroutDirs) {
|
|
153
|
-
await
|
|
156
|
+
await FileSystem.mkdir(skiroutDir, { recursive: true });
|
|
154
157
|
|
|
155
158
|
// Collect all the files in all the skirout dirs.
|
|
156
159
|
(
|
|
157
|
-
await glob(
|
|
160
|
+
await glob(Paths.join(skiroutDir, "**/*"), { withFileTypes: true })
|
|
158
161
|
).forEach((p) => preExistingAbsolutePaths.add(p.fullpath()));
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -169,7 +172,9 @@ class WatchModeMainLoop {
|
|
|
169
172
|
for (const file of files) {
|
|
170
173
|
const { path } = file;
|
|
171
174
|
if (pathToFile.has(path)) {
|
|
172
|
-
throw new
|
|
175
|
+
throw new ExitError(
|
|
176
|
+
"Multiple generators produce " + rewritePathForRendering(path),
|
|
177
|
+
);
|
|
173
178
|
}
|
|
174
179
|
pathToFile.set(path, file);
|
|
175
180
|
pathToGenerator.set(path, generator);
|
|
@@ -179,10 +184,10 @@ class WatchModeMainLoop {
|
|
|
179
184
|
for (
|
|
180
185
|
let pathToKeep = path;
|
|
181
186
|
pathToKeep !== ".";
|
|
182
|
-
pathToKeep =
|
|
187
|
+
pathToKeep = Paths.dirname(pathToKeep)
|
|
183
188
|
) {
|
|
184
189
|
preExistingAbsolutePaths.delete(
|
|
185
|
-
|
|
190
|
+
Paths.resolve(Paths.join(skiroutDir, pathToKeep)),
|
|
186
191
|
);
|
|
187
192
|
}
|
|
188
193
|
}
|
|
@@ -196,9 +201,9 @@ class WatchModeMainLoop {
|
|
|
196
201
|
const oldFile = lastWriteBatch.pathToFile.get(p);
|
|
197
202
|
const generator = pathToGenerator.get(p)!;
|
|
198
203
|
for (const skiroutDir of generator.skiroutDirs) {
|
|
199
|
-
const fsPath =
|
|
204
|
+
const fsPath = Paths.join(skiroutDir, p);
|
|
200
205
|
if (oldFile?.code === newFile.code) {
|
|
201
|
-
const mtime = (await
|
|
206
|
+
const mtime = (await FileSystem.stat(fsPath)).mtime;
|
|
202
207
|
if (
|
|
203
208
|
mtime !== null &&
|
|
204
209
|
mtime.getDate() <= lastWriteBatch.writeTime.getDate()
|
|
@@ -206,8 +211,8 @@ class WatchModeMainLoop {
|
|
|
206
211
|
return;
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
|
-
await
|
|
210
|
-
await
|
|
214
|
+
await FileSystem.mkdir(Paths.dirname(fsPath), { recursive: true });
|
|
215
|
+
await FileSystem.writeFile(fsPath, newFile.code, "utf-8");
|
|
211
216
|
}
|
|
212
217
|
}),
|
|
213
218
|
);
|
|
@@ -218,8 +223,8 @@ class WatchModeMainLoop {
|
|
|
218
223
|
.sort((a, b) => b.localeCompare(a, "en-US"))
|
|
219
224
|
.map(async (p) => {
|
|
220
225
|
try {
|
|
221
|
-
await
|
|
222
|
-
} catch (
|
|
226
|
+
await FileSystem.rm(p, { force: true, recursive: true });
|
|
227
|
+
} catch (_e) {
|
|
223
228
|
// Ignore error.
|
|
224
229
|
}
|
|
225
230
|
}),
|
|
@@ -240,25 +245,19 @@ class WatchModeMainLoop {
|
|
|
240
245
|
};
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
async function isDirectory(path: string): Promise<boolean> {
|
|
244
|
-
try {
|
|
245
|
-
return (await fs.lstat(path)).isDirectory();
|
|
246
|
-
} catch (e) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
248
|
function checkNoOverlappingSkiroutDirs(skiroutDirs: readonly string[]): void {
|
|
252
249
|
for (let i = 0; i < skiroutDirs.length; ++i) {
|
|
253
250
|
for (let j = i + 1; j < skiroutDirs.length; ++j) {
|
|
254
|
-
const dirA =
|
|
255
|
-
const dirB =
|
|
251
|
+
const dirA = Paths.normalize(skiroutDirs[i]!);
|
|
252
|
+
const dirB = Paths.normalize(skiroutDirs[j]!);
|
|
256
253
|
|
|
257
254
|
if (
|
|
258
|
-
dirA.startsWith(dirB +
|
|
259
|
-
dirB.startsWith(dirA +
|
|
255
|
+
dirA.startsWith(dirB + Paths.sep) ||
|
|
256
|
+
dirB.startsWith(dirA + Paths.sep)
|
|
260
257
|
) {
|
|
261
|
-
throw new
|
|
258
|
+
throw new ExitError(
|
|
259
|
+
`Overlapping skirout directories: ${dirA} and ${dirB}`,
|
|
260
|
+
);
|
|
262
261
|
}
|
|
263
262
|
}
|
|
264
263
|
}
|
|
@@ -270,7 +269,7 @@ interface ModuleFormatResult {
|
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
273
|
-
const skirFiles = await glob(
|
|
272
|
+
const skirFiles = await glob(Paths.join(root, "**/*.skir"), {
|
|
274
273
|
withFileTypes: true,
|
|
275
274
|
});
|
|
276
275
|
const pathToFormatResult = new Map<string, ModuleFormatResult>();
|
|
@@ -280,7 +279,9 @@ async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
|
280
279
|
}
|
|
281
280
|
const unformattedCode = REAL_FILE_SYSTEM.readTextFile(skirFile.fullpath());
|
|
282
281
|
if (unformattedCode === undefined) {
|
|
283
|
-
throw new
|
|
282
|
+
throw new ExitError(
|
|
283
|
+
"Cannot read " + rewritePathForRendering(skirFile.fullpath()),
|
|
284
|
+
);
|
|
284
285
|
}
|
|
285
286
|
const tokens = tokenizeModule(unformattedCode, "");
|
|
286
287
|
if (tokens.errors.length) {
|
|
@@ -295,7 +296,7 @@ async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
|
295
296
|
}
|
|
296
297
|
let numFilesNotFormatted = 0;
|
|
297
298
|
for (const [path, result] of pathToFormatResult) {
|
|
298
|
-
const relativePath =
|
|
299
|
+
const relativePath = Paths.relative(root, path).replace(/\\/g, "/");
|
|
299
300
|
if (mode === "fix") {
|
|
300
301
|
if (result.alreadyFormatted) {
|
|
301
302
|
console.log(`${makeGray(relativePath)} (unchanged)`);
|
|
@@ -333,14 +334,14 @@ async function main(): Promise<void> {
|
|
|
333
334
|
|
|
334
335
|
const root = args.root || ".";
|
|
335
336
|
|
|
336
|
-
if (!(await isDirectory(root
|
|
337
|
+
if (!(await isDirectory(root))) {
|
|
337
338
|
console.error(makeRed(`Not a directory: ${root}`));
|
|
338
339
|
process.exit(1);
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
switch (args.kind) {
|
|
342
343
|
case "init": {
|
|
343
|
-
initializeProject(root
|
|
344
|
+
initializeProject(root);
|
|
344
345
|
return;
|
|
345
346
|
}
|
|
346
347
|
case "help":
|
|
@@ -349,14 +350,7 @@ async function main(): Promise<void> {
|
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
!paths.isAbsolute(skirConfigPath) &&
|
|
355
|
-
!/^\.{1,2}[/\\]$/.test(skirConfigPath)
|
|
356
|
-
) {
|
|
357
|
-
// To make it clear that it's a path, prepend "./"
|
|
358
|
-
skirConfigPath = `.${paths.sep}${skirConfigPath}`;
|
|
359
|
-
}
|
|
353
|
+
const skirConfigPath = rewritePathForRendering(Paths.join(root, "skir.yml"));
|
|
360
354
|
const skirConfigCode = REAL_FILE_SYSTEM.readTextFile(skirConfigPath);
|
|
361
355
|
if (skirConfigCode === undefined) {
|
|
362
356
|
console.error(makeRed(`Cannot find ${skirConfigPath}`));
|
|
@@ -366,32 +360,30 @@ async function main(): Promise<void> {
|
|
|
366
360
|
const skirConfigResult = await parseSkirConfig(skirConfigCode, "import-mods");
|
|
367
361
|
if (skirConfigResult.errors.length > 0) {
|
|
368
362
|
console.error(makeRed("Invalid skir config"));
|
|
369
|
-
|
|
363
|
+
const { maybeForgotToEditAfterInit } = skirConfigResult;
|
|
364
|
+
renderSkirConfigErrors(skirConfigResult.errors, {
|
|
365
|
+
skirConfigPath,
|
|
366
|
+
maybeForgotToEditAfterInit,
|
|
367
|
+
});
|
|
370
368
|
process.exit(1);
|
|
371
369
|
}
|
|
372
370
|
const skirConfig = skirConfigResult.skirConfig!;
|
|
373
371
|
|
|
374
|
-
const srcDir =
|
|
372
|
+
const srcDir = Paths.join(root, skirConfig.srcDir || ".");
|
|
375
373
|
|
|
376
374
|
switch (args.kind) {
|
|
377
375
|
case "format": {
|
|
378
376
|
// Check or fix the formatting to the .skir files in the source directory.
|
|
379
|
-
await format(srcDir, args.subcommand === "
|
|
377
|
+
await format(srcDir, args.subcommand === "ci" ? "check" : "fix");
|
|
380
378
|
break;
|
|
381
379
|
}
|
|
382
380
|
case "gen": {
|
|
383
381
|
// Run the skir code generators in watch mode or once.
|
|
384
382
|
const generatorBundles: GeneratorBundle[] = await Promise.all(
|
|
385
383
|
skirConfig.generators.map((config) =>
|
|
386
|
-
makeGeneratorBundle(config, root
|
|
384
|
+
makeGeneratorBundle(config, root),
|
|
387
385
|
),
|
|
388
386
|
);
|
|
389
|
-
// Sort for consistency.
|
|
390
|
-
generatorBundles.sort((a, b) => {
|
|
391
|
-
const aId = a.generator.id;
|
|
392
|
-
const bId = b.generator.id;
|
|
393
|
-
return aId.localeCompare(bId, "en-US");
|
|
394
|
-
});
|
|
395
387
|
// Look for duplicates.
|
|
396
388
|
for (let i = 0; i < generatorBundles.length - 1; ++i) {
|
|
397
389
|
const { id } = generatorBundles[i]!.generator;
|
|
@@ -417,14 +409,17 @@ async function main(): Promise<void> {
|
|
|
417
409
|
case "snapshot": {
|
|
418
410
|
if (args.subcommand === "view") {
|
|
419
411
|
viewSnapshot({
|
|
420
|
-
rootDir: root
|
|
412
|
+
rootDir: root,
|
|
421
413
|
});
|
|
422
414
|
} else {
|
|
423
|
-
takeSnapshot({
|
|
424
|
-
rootDir: root
|
|
415
|
+
const success = takeSnapshot({
|
|
416
|
+
rootDir: root,
|
|
425
417
|
srcDir: srcDir,
|
|
426
|
-
|
|
418
|
+
subcommand: args.subcommand,
|
|
427
419
|
});
|
|
420
|
+
if (!success) {
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
428
423
|
}
|
|
429
424
|
break;
|
|
430
425
|
}
|
|
@@ -435,4 +430,14 @@ async function main(): Promise<void> {
|
|
|
435
430
|
}
|
|
436
431
|
}
|
|
437
432
|
|
|
438
|
-
|
|
433
|
+
try {
|
|
434
|
+
await main();
|
|
435
|
+
} catch (e) {
|
|
436
|
+
if (e instanceof Error) {
|
|
437
|
+
console.error(makeRed(e.message));
|
|
438
|
+
if (e instanceof ExitError) {
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
throw e;
|
|
443
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -2,13 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const GeneratorConfig = z.strictObject({
|
|
4
4
|
mod: z.string(),
|
|
5
|
+
outDir: z.union([
|
|
6
|
+
z.string().endsWith("/skirout"),
|
|
7
|
+
z.array(z.string().endsWith("/skirout")),
|
|
8
|
+
]),
|
|
5
9
|
config: z.any(),
|
|
6
|
-
outDir: z
|
|
7
|
-
.union([
|
|
8
|
-
z.string().endsWith("/skirout"),
|
|
9
|
-
z.array(z.string().endsWith("/skirout")),
|
|
10
|
-
])
|
|
11
|
-
.optional(),
|
|
12
10
|
});
|
|
13
11
|
|
|
14
12
|
export type GeneratorConfig = z.infer<typeof GeneratorConfig>;
|
package/src/config_parser.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
1
|
+
import * as CcGen from "skir-cc-gen";
|
|
2
|
+
import * as DartGen from "skir-dart-gen";
|
|
3
3
|
import { CodeGenerator } from "skir-internal";
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
4
|
+
import * as JavaGen from "skir-java-gen";
|
|
5
|
+
import * as KotlinGen from "skir-kotlin-gen";
|
|
6
|
+
import * as PythonGen from "skir-python-gen";
|
|
7
|
+
import * as TypescriptGen from "skir-typescript-gen";
|
|
8
8
|
import { LineCounter, parseDocument, Scalar, YAMLMap } from "yaml";
|
|
9
9
|
import { SkirConfig } from "./config.js";
|
|
10
10
|
|
|
11
11
|
export interface SkirConfigResult {
|
|
12
|
+
/** Defined if and only if `errors` is empty. */
|
|
12
13
|
skirConfig: SkirConfig | undefined;
|
|
13
14
|
errors: readonly SkirConfigError[];
|
|
15
|
+
/**
|
|
16
|
+
* If true, the user may have forgotten to edit skir.yml after running
|
|
17
|
+
* `npx skir init`.
|
|
18
|
+
*/
|
|
19
|
+
maybeForgotToEditAfterInit?: boolean;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface SkirConfigError {
|
|
@@ -66,6 +72,39 @@ export async function parseSkirConfig(
|
|
|
66
72
|
}
|
|
67
73
|
return offsetRangeToRange(node.range[0], node.range[1]);
|
|
68
74
|
};
|
|
75
|
+
const pushErrorAtPath = (
|
|
76
|
+
path: readonly PropertyKey[],
|
|
77
|
+
message: string,
|
|
78
|
+
): void => {
|
|
79
|
+
const pathRemainder: PropertyKey[] = [];
|
|
80
|
+
while (path.length !== 0) {
|
|
81
|
+
const range = pathToRange(path);
|
|
82
|
+
if (range) {
|
|
83
|
+
break;
|
|
84
|
+
} else {
|
|
85
|
+
// It's possible that 'path' does not map to a node if 'path' refers to
|
|
86
|
+
// a property which is missing. In that case, we pop the last element
|
|
87
|
+
// of 'path' and try again, until we find a node that exists. The
|
|
88
|
+
// elements which were popped will be included in the error message.
|
|
89
|
+
pathRemainder.push(path.at(-1)!);
|
|
90
|
+
path = path.slice(0, -1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
pathRemainder.reverse();
|
|
94
|
+
const pathRemainderStr = pathRemainder
|
|
95
|
+
.map((p, i) =>
|
|
96
|
+
typeof p === "number" ? `[${p}]` : i === 0 ? p : `.${String(p)}`,
|
|
97
|
+
)
|
|
98
|
+
.join("");
|
|
99
|
+
const messagePrefix = pathRemainder.length
|
|
100
|
+
? `Missing property '${pathRemainderStr}': `
|
|
101
|
+
: "";
|
|
102
|
+
const range = pathToRange(path);
|
|
103
|
+
errors.push({
|
|
104
|
+
message: messagePrefix + message,
|
|
105
|
+
range: range,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
69
108
|
|
|
70
109
|
// Check for YAML parsing errors
|
|
71
110
|
if (doc.errors.length > 0) {
|
|
@@ -76,24 +115,27 @@ export async function parseSkirConfig(
|
|
|
76
115
|
range: range,
|
|
77
116
|
});
|
|
78
117
|
}
|
|
79
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
skirConfig: undefined,
|
|
120
|
+
errors: errors,
|
|
121
|
+
};
|
|
80
122
|
}
|
|
81
123
|
|
|
82
|
-
const jsData = doc.toJS();
|
|
83
|
-
|
|
84
124
|
// 2. Validate with Zod schema
|
|
125
|
+
const jsData = doc.toJS();
|
|
85
126
|
const result = SkirConfig.safeParse(jsData);
|
|
86
127
|
|
|
87
128
|
if (!result.success) {
|
|
88
129
|
for (const issue of result.error.issues) {
|
|
89
|
-
|
|
90
|
-
const range = pathToRange(issue.path);
|
|
91
|
-
errors.push({
|
|
92
|
-
message: issue.message,
|
|
93
|
-
range: range,
|
|
94
|
-
});
|
|
130
|
+
pushErrorAtPath(issue.path, issue.message);
|
|
95
131
|
}
|
|
96
|
-
|
|
132
|
+
const maybeForgotToEditAfterInit =
|
|
133
|
+
jsData && typeof jsData === "object" && jsData.generators === null;
|
|
134
|
+
return {
|
|
135
|
+
skirConfig: undefined,
|
|
136
|
+
errors: errors,
|
|
137
|
+
maybeForgotToEditAfterInit,
|
|
138
|
+
};
|
|
97
139
|
}
|
|
98
140
|
|
|
99
141
|
// 3. Validate each generator's config with Zod schema
|
|
@@ -106,11 +148,7 @@ export async function parseSkirConfig(
|
|
|
106
148
|
generator = await importCodeGenerator(mod);
|
|
107
149
|
} catch (e) {
|
|
108
150
|
if (e instanceof Error) {
|
|
109
|
-
|
|
110
|
-
errors.push({
|
|
111
|
-
message: e.message,
|
|
112
|
-
range: range,
|
|
113
|
-
});
|
|
151
|
+
pushErrorAtPath(["generators", i, "mod"], e.message);
|
|
114
152
|
continue;
|
|
115
153
|
} else {
|
|
116
154
|
throw e;
|
|
@@ -119,13 +157,13 @@ export async function parseSkirConfig(
|
|
|
119
157
|
} else {
|
|
120
158
|
// TODO: rm the casts
|
|
121
159
|
const modToGenerator: Record<string, CodeGenerator<unknown>> = {
|
|
122
|
-
"skir-cc-gen":
|
|
123
|
-
"skir-dart-gen":
|
|
124
|
-
"skir-java-gen":
|
|
125
|
-
"skir-kotlin-gen":
|
|
126
|
-
"skir-python-gen":
|
|
160
|
+
"skir-cc-gen": CcGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
161
|
+
"skir-dart-gen": DartGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
162
|
+
"skir-java-gen": JavaGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
163
|
+
"skir-kotlin-gen": KotlinGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
164
|
+
"skir-python-gen": PythonGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
127
165
|
"skir-typescript-gen":
|
|
128
|
-
|
|
166
|
+
TypescriptGen.GENERATOR as any as CodeGenerator<unknown>,
|
|
129
167
|
};
|
|
130
168
|
generator = modToGenerator[mod];
|
|
131
169
|
}
|
|
@@ -141,11 +179,7 @@ export async function parseSkirConfig(
|
|
|
141
179
|
"config",
|
|
142
180
|
...issue.path,
|
|
143
181
|
];
|
|
144
|
-
|
|
145
|
-
errors.push({
|
|
146
|
-
message: issue.message ?? "Error",
|
|
147
|
-
range: range,
|
|
148
|
-
});
|
|
182
|
+
pushErrorAtPath(path, issue.message ?? "Error");
|
|
149
183
|
}
|
|
150
184
|
}
|
|
151
185
|
}
|
package/src/error_renderer.ts
CHANGED
|
@@ -61,6 +61,7 @@ export function renderSkirConfigErrors(
|
|
|
61
61
|
errors: readonly SkirConfigError[],
|
|
62
62
|
context: {
|
|
63
63
|
skirConfigPath: string;
|
|
64
|
+
maybeForgotToEditAfterInit: boolean | undefined;
|
|
64
65
|
},
|
|
65
66
|
): void {
|
|
66
67
|
for (const error of errors) {
|
|
@@ -68,6 +69,13 @@ export function renderSkirConfigErrors(
|
|
|
68
69
|
console.error(formatSkirConfigError(error, context));
|
|
69
70
|
}
|
|
70
71
|
console.error();
|
|
72
|
+
if (context.maybeForgotToEditAfterInit) {
|
|
73
|
+
const { skirConfigPath } = context;
|
|
74
|
+
console.warn(
|
|
75
|
+
`Did you forget to edit ${skirConfigPath} after running 'npx skir init'?`,
|
|
76
|
+
);
|
|
77
|
+
console.warn();
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function formatSkirConfigError(
|
|
@@ -179,9 +187,9 @@ function formatBreakingChange(
|
|
|
179
187
|
const { enumEpression, number, record, variantName } = breakingChange;
|
|
180
188
|
const errorHeader = makeRed("Illegal variant kind change");
|
|
181
189
|
const enumName = map(record, getQualifiedName);
|
|
182
|
-
const variantKind = map(variantName, (vn) =>
|
|
183
|
-
caseMatches(vn.text, "lower_underscore") ? "wrapper" : "constant"
|
|
184
|
-
|
|
190
|
+
const variantKind = map(variantName, (vn) =>
|
|
191
|
+
caseMatches(vn.text, "lower_underscore") ? "wrapper" : "constant",
|
|
192
|
+
);
|
|
185
193
|
return [
|
|
186
194
|
`${locationPrefix}${errorHeader}\n`,
|
|
187
195
|
" [Last snapshot]\n",
|
package/src/io.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as FileSystem from "fs";
|
|
2
|
+
import * as FileSystemPromises from "fs/promises";
|
|
3
|
+
import * as Paths from "path";
|
|
2
4
|
|
|
3
5
|
export interface FileReader {
|
|
4
6
|
readTextFile(path: string): string | undefined;
|
|
@@ -11,7 +13,7 @@ export interface FileWriter {
|
|
|
11
13
|
class RealFileSystem implements FileReader, FileWriter {
|
|
12
14
|
readTextFile(path: string): string | undefined {
|
|
13
15
|
try {
|
|
14
|
-
return
|
|
16
|
+
return FileSystem.readFileSync(path, "utf-8");
|
|
15
17
|
} catch (error) {
|
|
16
18
|
if (
|
|
17
19
|
error &&
|
|
@@ -26,8 +28,25 @@ class RealFileSystem implements FileReader, FileWriter {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
writeTextFile(path: string, contents: string): void {
|
|
29
|
-
|
|
31
|
+
FileSystem.writeFileSync(path, contents, "utf-8");
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export const REAL_FILE_SYSTEM = new RealFileSystem();
|
|
36
|
+
|
|
37
|
+
export async function isDirectory(path: string): Promise<boolean> {
|
|
38
|
+
try {
|
|
39
|
+
return (await FileSystemPromises.lstat(path)).isDirectory();
|
|
40
|
+
} catch (_e) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function rewritePathForRendering(path: string): string {
|
|
46
|
+
if (Paths.isAbsolute(path) || /^\.{1,2}[/\\]$/.test(path)) {
|
|
47
|
+
return path;
|
|
48
|
+
} else {
|
|
49
|
+
// To make it clear that it's a path, prepend "./"
|
|
50
|
+
return `.${Paths.sep}${path}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/module_collector.ts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
import * as
|
|
3
|
-
import {
|
|
2
|
+
import * as Paths from "path";
|
|
3
|
+
import { ExitError } from "./exit_error.js";
|
|
4
|
+
import {
|
|
5
|
+
isDirectory,
|
|
6
|
+
REAL_FILE_SYSTEM,
|
|
7
|
+
rewritePathForRendering,
|
|
8
|
+
} from "./io.js";
|
|
4
9
|
import { ModuleSet } from "./module_set.js";
|
|
5
10
|
|
|
6
11
|
export async function collectModules(srcDir: string): Promise<ModuleSet> {
|
|
7
12
|
const modules = ModuleSet.create(REAL_FILE_SYSTEM, srcDir);
|
|
8
|
-
const skirFiles = await glob(
|
|
13
|
+
const skirFiles = await glob(Paths.join(srcDir, "**/*.skir"), {
|
|
9
14
|
stat: true,
|
|
10
15
|
withFileTypes: true,
|
|
11
16
|
});
|
|
12
|
-
|
|
17
|
+
if (skirFiles.length === 0) {
|
|
18
|
+
const isDir = await isDirectory(srcDir);
|
|
19
|
+
if (!isDir) {
|
|
20
|
+
throw new ExitError(
|
|
21
|
+
"Source directory does not exist: " + rewritePathForRendering(srcDir),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const skirFile of skirFiles) {
|
|
13
26
|
if (!skirFile.isFile) {
|
|
14
27
|
continue;
|
|
15
28
|
}
|
|
16
|
-
const relativePath =
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
const relativePath = Paths.relative(srcDir, skirFile.fullpath()).replace(
|
|
30
|
+
/\\/g,
|
|
31
|
+
"/",
|
|
32
|
+
);
|
|
19
33
|
modules.parseAndResolve(relativePath);
|
|
20
34
|
}
|
|
21
35
|
return modules;
|
package/src/module_set.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as Paths from "path";
|
|
2
2
|
import {
|
|
3
3
|
MutableDocReferenceName,
|
|
4
4
|
unquoteAndUnescape,
|
|
@@ -1311,7 +1311,7 @@ class DefaultModuleParser extends ModuleParserBase {
|
|
|
1311
1311
|
}
|
|
1312
1312
|
|
|
1313
1313
|
readSourceCode(modulePath: string): string | undefined {
|
|
1314
|
-
return this.fileReader.readTextFile(
|
|
1314
|
+
return this.fileReader.readTextFile(Paths.join(this.rootPath, modulePath));
|
|
1315
1315
|
}
|
|
1316
1316
|
}
|
|
1317
1317
|
|
|
@@ -1341,12 +1341,12 @@ function resolveModulePath(
|
|
|
1341
1341
|
if (modulePath.startsWith("./") || modulePath.startsWith("../")) {
|
|
1342
1342
|
// This is a relative path from the module. Let's transform it into a
|
|
1343
1343
|
// relative path from root.
|
|
1344
|
-
modulePath =
|
|
1344
|
+
modulePath = Paths.join(originModulePath, "..", modulePath);
|
|
1345
1345
|
}
|
|
1346
1346
|
// "a/./b/../c" => "a/c"
|
|
1347
1347
|
// Note that `paths.normalize` will use backslashes on Windows.
|
|
1348
1348
|
// We don't want that.
|
|
1349
|
-
modulePath =
|
|
1349
|
+
modulePath = Paths.normalize(modulePath).replace(/\\/g, "/");
|
|
1350
1350
|
if (modulePath.startsWith(`../`)) {
|
|
1351
1351
|
errors.push({
|
|
1352
1352
|
token: pathToken,
|
package/src/parser.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
UnresolvedType,
|
|
29
29
|
} from "skir-internal";
|
|
30
30
|
import { convertCase, simpleHash } from "skir-internal";
|
|
31
|
-
import * as
|
|
31
|
+
import * as Casing from "./casing.js";
|
|
32
32
|
import { mergeDocs } from "./doc_comment_parser.js";
|
|
33
33
|
import { ModuleTokens } from "./tokenizer.js";
|
|
34
34
|
|
|
@@ -398,7 +398,7 @@ function parseRecord(
|
|
|
398
398
|
if (nameMatch.case < 0) {
|
|
399
399
|
return null;
|
|
400
400
|
}
|
|
401
|
-
|
|
401
|
+
Casing.validate(nameMatch.token, "UpperCamel", it.errors);
|
|
402
402
|
nameToken = nameMatch.token;
|
|
403
403
|
}
|
|
404
404
|
let stableId: number | null = null;
|
|
@@ -476,7 +476,7 @@ function parseField(
|
|
|
476
476
|
}
|
|
477
477
|
case 2: {
|
|
478
478
|
const expectedCasing = type ? "lower_underscore" : "UPPER_UNDERSCORE";
|
|
479
|
-
|
|
479
|
+
Casing.validate(name, expectedCasing, it.errors);
|
|
480
480
|
if (recordType === "enum" && name.text === "UNKNOWN") {
|
|
481
481
|
it.errors.push({
|
|
482
482
|
token: name,
|
|
@@ -790,7 +790,7 @@ function parseImportAs(it: TokenIterator): ImportAlias | null {
|
|
|
790
790
|
if (aliasMatch.case < 0) {
|
|
791
791
|
return null;
|
|
792
792
|
}
|
|
793
|
-
|
|
793
|
+
Casing.validate(aliasMatch.token, "lower_underscore", it.errors);
|
|
794
794
|
if (it.expectThenNext(["from"]).case < 0) return null;
|
|
795
795
|
const modulePathMatch = it.expectThenNext([TOKEN_IS_STRING_LITERAL]);
|
|
796
796
|
if (modulePathMatch.case < 0) {
|
|
@@ -838,7 +838,7 @@ function parseMethod(it: TokenIterator, doc: Doc): MutableMethod | null {
|
|
|
838
838
|
return null;
|
|
839
839
|
}
|
|
840
840
|
const name = nameMatch.token;
|
|
841
|
-
|
|
841
|
+
Casing.validate(name, "UpperCamel", it.errors);
|
|
842
842
|
if (it.expectThenNext(["("]).case < 0) {
|
|
843
843
|
return null;
|
|
844
844
|
}
|
|
@@ -898,7 +898,7 @@ function parseConstant(it: TokenIterator, doc: Doc): MutableConstant | null {
|
|
|
898
898
|
if (nameMatch.case < 0) {
|
|
899
899
|
return null;
|
|
900
900
|
}
|
|
901
|
-
|
|
901
|
+
Casing.validate(nameMatch.token, "UPPER_UNDERSCORE", it.errors);
|
|
902
902
|
if (it.expectThenNext([":"]).case < 0) {
|
|
903
903
|
return null;
|
|
904
904
|
}
|