@travetto/compiler 8.0.0-alpha.2 → 8.0.0-alpha.21

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/src/state.ts CHANGED
@@ -1,25 +1,29 @@
1
- import type { CompilerHost, SourceFile, CompilerOptions, Program, ScriptTarget } from 'typescript';
2
-
3
- import { path, ManifestModuleUtil, type ManifestModule, type ManifestRoot, type ManifestIndex, type ManifestModuleFolderType } from '@travetto/manifest';
1
+ import fs from 'node:fs';
2
+
3
+ import type { CompilerHost, CompilerOptions, Program, ScriptTarget, SourceFile } from 'typescript';
4
+
5
+ import {
6
+ type ManifestIndex,
7
+ type ManifestModule,
8
+ type ManifestModuleFolderType,
9
+ ManifestModuleUtil,
10
+ type ManifestRoot,
11
+ path
12
+ } from '@travetto/manifest';
4
13
  import type { TransformerManager } from '@travetto/transformer';
5
14
 
6
- import { CompilerUtil } from './util.ts';
7
- import type { CompileEmitError, CompileStateEntry } from './types.ts';
8
15
  import { CommonUtil } from './common.ts';
9
16
  import { tsProxy as ts, tsProxyInit } from './ts-proxy.ts';
10
-
17
+ import type { CompileStateEntry } from './types.ts';
18
+ import { CompilerUtil } from './util.ts';
11
19
 
12
20
  const TYPINGS_FOLDER_KEYS = new Set<ManifestModuleFolderType>(['$index', 'support', 'src', '$package']);
13
21
 
14
22
  export class CompilerState implements CompilerHost {
15
-
16
23
  static async get(idx: ManifestIndex): Promise<CompilerState> {
17
24
  return new CompilerState().init(idx);
18
25
  }
19
26
 
20
- /** @private */
21
- constructor() { }
22
-
23
27
  #outputPath: string;
24
28
  #typingsPath: string;
25
29
  #sourceFiles = new Set<string>();
@@ -40,10 +44,43 @@ export class CompilerState implements CompilerHost {
40
44
  #program: Program;
41
45
 
42
46
  #readFile(sourceFile: string): string | undefined {
43
- return ts.sys.readFile(this.#sourceToEntry.get(sourceFile)?.sourceFile ?? sourceFile);
47
+ const location = this.#sourceToEntry.get(sourceFile)?.sourceFile ?? sourceFile;
48
+ try {
49
+ return ts.sys.readFile(location, 'utf8');
50
+ } catch {
51
+ try {
52
+ return fs.readFileSync(location, 'utf8');
53
+ } catch {}
54
+ }
55
+ return undefined;
56
+ }
57
+
58
+ #writeFile(location: string, text: string, bom?: boolean): void {
59
+ try {
60
+ ts.sys.writeFile(location, text, bom);
61
+ } catch {
62
+ fs.mkdirSync(path.dirname(location), { recursive: true });
63
+ fs.writeFileSync(location, text, 'utf8');
64
+ }
65
+ }
66
+
67
+ #fileExists(location: string): boolean {
68
+ try {
69
+ return ts.sys.fileExists(location);
70
+ } catch {
71
+ return fs.existsSync(location);
72
+ }
44
73
  }
45
74
 
46
- #writeExternalTypings(location: string, text: string, bom: boolean): void {
75
+ #directoryExists(location: string): boolean {
76
+ try {
77
+ return ts.sys.directoryExists(location);
78
+ } catch {
79
+ return fs.existsSync(location);
80
+ }
81
+ }
82
+
83
+ #writeExternalTypings(location: string, text: string, bom?: boolean): void {
47
84
  let core = location.replace('.map', '');
48
85
  if (!this.#outputToEntry.has(core)) {
49
86
  core = core.replace(ManifestModuleUtil.TYPINGS_EXT_REGEX, ManifestModuleUtil.OUTPUT_EXT);
@@ -52,7 +89,7 @@ export class CompilerState implements CompilerHost {
52
89
  if (entry) {
53
90
  const relative = this.#manifestIndex.getFromSource(entry.sourceFile)?.relativeFile;
54
91
  if (relative && TYPINGS_FOLDER_KEYS.has(ManifestModuleUtil.getFolderKey(relative))) {
55
- ts.sys.writeFile(location.replace(this.#outputPath, this.#typingsPath), text, bom);
92
+ this.#writeFile(location.replace(this.#outputPath, this.#typingsPath), text, bom);
56
93
  }
57
94
  }
58
95
  }
@@ -60,7 +97,7 @@ export class CompilerState implements CompilerHost {
60
97
  async #initCompilerOptions(): Promise<CompilerOptions> {
61
98
  const tsconfigFile = CommonUtil.resolveWorkspace(this.#manifest, 'tsconfig.json');
62
99
  if (!ts.sys.fileExists(tsconfigFile)) {
63
- ts.sys.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }, null, 2));
100
+ this.#writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }, null, 2));
64
101
  }
65
102
 
66
103
  const { options } = ts.parseJsonSourceFileConfigFileContent(
@@ -72,9 +109,7 @@ export class CompilerState implements CompilerHost {
72
109
  return {
73
110
  ...options,
74
111
  noEmit: false,
75
- emitDeclarationOnly: false,
76
112
  allowJs: true,
77
- resolveJsonModule: true,
78
113
  sourceRoot: this.#manifest.workspace.path,
79
114
  rootDir: this.#manifest.workspace.path,
80
115
  moduleResolution: ts.ModuleResolutionKind.Bundler,
@@ -95,14 +130,14 @@ export class CompilerState implements CompilerHost {
95
130
  for (const module of this.#modules) {
96
131
  const base = module?.files ?? {};
97
132
  const files = [
98
- ...base.bin ?? [],
99
- ...base.src ?? [],
100
- ...base.support ?? [],
101
- ...base.doc ?? [],
102
- ...base.test ?? [],
103
- ...base.$transformer ?? [],
104
- ...base.$index ?? [],
105
- ...base.$package ?? []
133
+ ...(base.bin ?? []),
134
+ ...(base.src ?? []),
135
+ ...(base.support ?? []),
136
+ ...(base.doc ?? []),
137
+ ...(base.test ?? []),
138
+ ...(base.$transformer ?? []),
139
+ ...(base.$index ?? []),
140
+ ...(base.$package ?? [])
106
141
  ];
107
142
  for (const [file, type] of files) {
108
143
  if (ManifestModuleUtil.isSourceType(type)) {
@@ -133,52 +168,80 @@ export class CompilerState implements CompilerHost {
133
168
  }
134
169
 
135
170
  getArbitraryInputFile(): string {
136
- const randomSource = this.#manifestIndex.getWorkspaceModules()
137
- .filter(module => module.files.src?.length)[0]
138
- .files.src[0].sourceFile;
171
+ const randomSource = this.#manifestIndex.getWorkspaceModules().filter(module => module.files.src?.length)[0].files.src[0].sourceFile;
139
172
 
140
173
  return this.getBySource(randomSource)!.sourceFile;
141
174
  }
142
175
 
143
- async createProgram(force = false): Promise<Program> {
176
+ async getProgram(force = false): Promise<Program> {
144
177
  if (force || !this.#program) {
145
- this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
178
+ await this.initializeTypescript();
179
+ this.#program = ts.createProgram({
180
+ rootNames: this.getAllFiles(),
181
+ host: this,
182
+ options: this.#compilerOptions,
183
+ oldProgram: this.#program
184
+ });
146
185
  this.#transformerManager.init(this.#program.getTypeChecker());
147
186
  await CommonUtil.queueMacroTask();
148
187
  }
149
188
  return this.#program;
150
189
  }
151
190
 
152
- async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<CompileEmitError | undefined> {
191
+ async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<string[] | undefined> {
153
192
  const output = this.#sourceToEntry.get(sourceFile)?.outputFile;
154
193
  if (!output) {
155
194
  return;
156
195
  }
157
196
 
158
- const program = await this.createProgram(needsNewProgram);
159
- try {
160
- switch (ManifestModuleUtil.getFileType(sourceFile)) {
161
- case 'typings':
162
- case 'package-json':
163
- this.writeFile(output, this.readFile(sourceFile)!, false), undefined;
164
- break;
165
- case 'js':
166
- this.writeFile(output, ts.transpile(this.readFile(sourceFile)!, this.#compilerOptions), false);
167
- break;
168
- case 'ts': {
169
- const result = program.emit(
170
- program.getSourceFile(sourceFile)!,
171
- (...args) => this.writeFile(args[0], args[1], args[2]), undefined, false,
172
- this.#transformerManager.get()
173
- );
174
- return result?.diagnostics?.length ? result.diagnostics : undefined;
175
- }
197
+ switch (ManifestModuleUtil.getFileType(sourceFile)) {
198
+ case 'package-json': {
199
+ const text = this.readFile(sourceFile)!;
200
+ const finalText = CompilerUtil.rewritePackageJSON(this.#manifest, text);
201
+ const location = this.#tscOutputFileToOuptut.get(output) ?? output;
202
+ this.#writeFile(location, finalText);
203
+ this.#writeExternalTypings(location, finalText);
204
+ break;
176
205
  }
177
- } catch (error) {
178
- if (error instanceof Error) {
179
- return error;
180
- } else {
181
- throw error;
206
+ case 'js':
207
+ case 'typings':
208
+ this.writeFile(output, this.readFile(sourceFile)!);
209
+ break;
210
+ case 'ts': {
211
+ const program = await this.getProgram(needsNewProgram);
212
+ const tsSourceFile = program.getSourceFile(sourceFile)!;
213
+ program.emit(
214
+ tsSourceFile,
215
+ (...args) => this.writeFile(args[0], args[1], args[2]),
216
+ undefined,
217
+ false,
218
+ this.#transformerManager.get()
219
+ );
220
+ return [
221
+ ...program.getSemanticDiagnostics(tsSourceFile),
222
+ ...program.getSyntacticDiagnostics(tsSourceFile),
223
+ ...program.getDeclarationDiagnostics(tsSourceFile)
224
+ ]
225
+ .filter(d => d.category === ts.DiagnosticCategory.Error)
226
+ .map(diag => {
227
+ let message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
228
+ if (
229
+ message.includes("is not under 'rootDir'") ||
230
+ message.includes("does not exist on type 'EnvDataCombinedType'") ||
231
+ message.startsWith('Could not find a declaration file for module') ||
232
+ message.startsWith("Cannot find module '@travetto") ||
233
+ message.startsWith("This JSX tag requires the module path '@travetto") ||
234
+ message.startsWith("JSX element implicitly has type 'any'")
235
+ ) {
236
+ return '';
237
+ }
238
+ if (diag.file) {
239
+ const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
240
+ message = `${line + 1}:${character + 1} -- ${message}`;
241
+ }
242
+ return message;
243
+ })
244
+ .filter(Boolean);
182
245
  }
183
246
  }
184
247
  }
@@ -189,7 +252,11 @@ export class CompilerState implements CompilerHost {
189
252
 
190
253
  isCompilerFile(file: string): boolean {
191
254
  const entry = this.getBySource(file);
192
- return (entry?.moduleFile && ManifestModuleUtil.getFileRole(entry.moduleFile) === 'compile') || entry?.module.roles.includes('compile') || false;
255
+ return (
256
+ (entry?.moduleFile && ManifestModuleUtil.getFileRole(entry.moduleFile) === 'compile') ||
257
+ entry?.module.roles.includes('compile') ||
258
+ false
259
+ );
193
260
  }
194
261
 
195
262
  registerInput(module: ManifestModule, moduleFile: string): CompileStateEntry {
@@ -263,40 +330,44 @@ export class CompilerState implements CompilerHost {
263
330
  }
264
331
 
265
332
  /* Start Compiler Host */
266
- getCanonicalFileName(file: string): string { return file; }
267
- getCurrentDirectory(): string { return this.#manifest.workspace.path; }
268
- getDefaultLibFileName(options: CompilerOptions): string { return ts.getDefaultLibFileName(options); }
269
- getNewLine(): string { return ts.sys.newLine; }
270
- useCaseSensitiveFileNames(): boolean { return ts.sys.useCaseSensitiveFileNames; }
271
- getDefaultLibLocation(): string { return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions)); }
333
+ getCanonicalFileName(file: string): string {
334
+ return file;
335
+ }
336
+ getCurrentDirectory(): string {
337
+ return this.#manifest.workspace.path;
338
+ }
339
+ getDefaultLibFileName(options: CompilerOptions): string {
340
+ return ts.getDefaultLibFileName(options);
341
+ }
342
+ getNewLine(): string {
343
+ return ts.sys.newLine;
344
+ }
345
+ useCaseSensitiveFileNames(): boolean {
346
+ return ts.sys.useCaseSensitiveFileNames;
347
+ }
348
+ getDefaultLibLocation(): string {
349
+ return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions));
350
+ }
272
351
 
273
352
  fileExists(sourceFile: string): boolean {
274
- return this.#sourceToEntry.has(sourceFile) || ts.sys.fileExists(sourceFile);
353
+ return this.#sourceToEntry.has(sourceFile) || this.#fileExists(sourceFile);
275
354
  }
276
355
 
277
356
  directoryExists(sourceDirectory: string): boolean {
278
- return this.#sourceDirectory.has(sourceDirectory) || ts.sys.directoryExists(sourceDirectory);
357
+ return this.#sourceDirectory.has(sourceDirectory) || this.#directoryExists(sourceDirectory);
279
358
  }
280
359
 
281
- writeFile(
282
- outputFile: string,
283
- text: string,
284
- bom: boolean
285
- ): void {
286
- if (outputFile.endsWith('package.json')) {
287
- text = CompilerUtil.rewritePackageJSON(this.#manifest, text);
288
- }
289
-
360
+ writeFile(outputFile: string, text: string, bom?: boolean): void {
290
361
  // JSX runtime shenanigans
291
362
  text = text.replace(/support\/jsx-runtime"/g, 'support/jsx-runtime.js"');
292
363
 
293
364
  const location = this.#tscOutputFileToOuptut.get(outputFile) ?? outputFile;
294
365
 
295
- if (ManifestModuleUtil.TYPINGS_WITH_MAP_EXT_REGEX.test(outputFile) || outputFile.endsWith('package.json')) {
366
+ if (ManifestModuleUtil.TYPINGS_WITH_MAP_EXT_REGEX.test(outputFile)) {
296
367
  this.#writeExternalTypings(location, text, bom);
297
368
  }
298
369
 
299
- ts.sys.writeFile(location, text, bom);
370
+ this.#writeFile(location, text, bom);
300
371
  }
301
372
 
302
373
  readFile(sourceFile: string): string | undefined {
@@ -311,4 +382,4 @@ export class CompilerState implements CompilerHost {
311
382
  return ts.createSourceFile(sourceFile, content ?? '', language);
312
383
  });
313
384
  }
314
- }
385
+ }
package/src/ts-proxy.ts CHANGED
@@ -1,11 +1,14 @@
1
- /* eslint-disable @typescript-eslint/consistent-type-assertions */
2
1
  import type ts from 'typescript';
3
2
 
4
3
  let state: typeof ts | undefined;
5
- export const tsProxyInit = (): Promise<unknown> => import('typescript').then(module => { state = module.default; });
4
+ let promise: Promise<unknown> | undefined;
5
+ export const tsProxyInit = (): Promise<unknown> =>
6
+ (promise ??= import('typescript').then(module => {
7
+ state = module.default;
8
+ }));
6
9
 
7
10
  export const tsProxy = new Proxy({}!, {
8
11
  get(_, prop: string): unknown {
9
12
  return state![prop as keyof typeof ts];
10
13
  }
11
- }) as typeof ts;
14
+ }) as typeof ts;
package/src/types.ts CHANGED
@@ -1,29 +1,32 @@
1
- import type ts from 'typescript';
2
-
3
1
  import type { ChangeEventType, ManifestModule } from '@travetto/manifest';
4
2
 
5
3
  export type CompilerStateType = 'startup' | 'init' | 'compile-start' | 'compile-end' | 'watch-start' | 'watch-end' | 'reset' | 'closed';
6
4
  export type CompilerLogLevel = 'info' | 'debug' | 'warn' | 'error';
7
5
 
8
- export type CompileEmitError = Error | readonly ts.Diagnostic[];
9
- export type CompileEmitter = (file: string, newProgram?: boolean) => Promise<CompileEmitError | undefined>;
10
- export type CompileEmitEvent = { file: string, i: number, total: number, error?: CompileEmitError, duration: number };
11
- export type CompileStateEntry = { sourceFile: string, tscOutputFile: string, outputFile?: string, module: ManifestModule, import: string, moduleFile: string };
12
- export type CompilerWatchEvent = { action: ChangeEventType, file: string, entry: CompileStateEntry, moduleFile: string };
6
+ export type CompileEmitEvent = { file: string; i: number; total: number; errors?: string[]; duration: number };
7
+ export type CompileStateEntry = {
8
+ sourceFile: string;
9
+ tscOutputFile: string;
10
+ outputFile?: string;
11
+ module: ManifestModule;
12
+ import: string;
13
+ moduleFile: string;
14
+ };
15
+ export type CompilerWatchEvent = { action: ChangeEventType; file: string; entry: CompileStateEntry; moduleFile: string };
13
16
 
14
- export type CompilerChangeEvent = { file: string, action: ChangeEventType, output: string, module: string, import: string, time: number };
15
- export type CompilerLogEvent = { level: CompilerLogLevel, message: string, time?: number, args?: unknown[], scope?: string };
16
- export type CompilerProgressEvent = { idx: number, total: number, message: string, operation: 'compile', complete?: boolean };
17
- export type CompilerStateEvent = { state: CompilerStateType, extra?: Record<string, unknown> };
18
- export type FileChangeEvent = { files: { file: string, action: ChangeEventType }[], time: number };
17
+ export type CompilerChangeEvent = { file: string; action: ChangeEventType; output: string; module: string; import: string; time: number };
18
+ export type CompilerLogEvent = { level: CompilerLogLevel; message: string; time?: number; args?: unknown[]; scope?: string };
19
+ export type CompilerProgressEvent = { idx: number; total: number; message: string; operation: 'compile'; complete?: boolean };
20
+ export type CompilerStateEvent = { state: CompilerStateType; extra?: Record<string, unknown> };
21
+ export type FileChangeEvent = { files: { file: string; action: ChangeEventType }[]; time: number };
19
22
 
20
23
  export type CompilerEvent =
21
- { type: 'file', payload: FileChangeEvent } |
22
- { type: 'change', payload: CompilerChangeEvent } |
23
- { type: 'log', payload: CompilerLogEvent } |
24
- { type: 'progress', payload: CompilerProgressEvent } |
25
- { type: 'state', payload: CompilerStateEvent } |
26
- { type: 'all', payload: unknown };
24
+ | { type: 'file'; payload: FileChangeEvent }
25
+ | { type: 'change'; payload: CompilerChangeEvent }
26
+ | { type: 'log'; payload: CompilerLogEvent }
27
+ | { type: 'progress'; payload: CompilerProgressEvent }
28
+ | { type: 'state'; payload: CompilerStateEvent }
29
+ | { type: 'all'; payload: unknown };
27
30
 
28
31
  export type CompilerEventType = CompilerEvent['type'];
29
32
  export type CompilerEventPayload<V> = (CompilerEvent & { type: V })['payload'];
@@ -39,4 +42,4 @@ export type CompilerServerInfo = {
39
42
  env?: Record<string, string>;
40
43
  };
41
44
 
42
- export class CompilerReset extends Error { }
45
+ export class CompilerReset extends Error {}
package/src/util.ts CHANGED
@@ -1,15 +1,9 @@
1
1
  import { ManifestModuleUtil, type ManifestRoot, type Package } from '@travetto/manifest';
2
2
 
3
- import type { CompileEmitError } from './types.ts';
4
- import { tsProxy as ts } from './ts-proxy.ts';
5
-
6
- const nativeCwd = process.cwd();
7
-
8
3
  /**
9
4
  * Standard utilities for compiler
10
5
  */
11
6
  export class CompilerUtil {
12
-
13
7
  /**
14
8
  * Rewrites the package.json to target output file names, and pins versions
15
9
  * @param manifest
@@ -38,32 +32,6 @@ export class CompilerUtil {
38
32
  return JSON.stringify(pkg, null, 2);
39
33
  }
40
34
 
41
- /**
42
- * Build transpilation error
43
- * @param filename The name of the file
44
- * @param diagnostics The diagnostic errors
45
- */
46
- static buildTranspileError(filename: string, diagnostics: CompileEmitError): Error {
47
- if (diagnostics instanceof Error) {
48
- return diagnostics;
49
- }
50
-
51
- const errors: string[] = diagnostics.slice(0, 5).map(diag => {
52
- const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
53
- if (diag.file) {
54
- const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
55
- return ` @ ${diag.file.fileName.replace(nativeCwd, '.')}(${line + 1}, ${character + 1}): ${message}`;
56
- } else {
57
- return ` ${message}`;
58
- }
59
- });
60
-
61
- if (diagnostics.length > 5) {
62
- errors.push(`${diagnostics.length - 5} more ...`);
63
- }
64
- return new Error(`Transpiling ${filename.replace(nativeCwd, '.')} failed: \n${errors.join('\n')}`);
65
- }
66
-
67
35
  /**
68
36
  * Naive hashing
69
37
  */
@@ -71,10 +39,9 @@ export class CompilerUtil {
71
39
  let hash = 5381;
72
40
 
73
41
  for (let i = 0; i < text.length; i++) {
74
- // eslint-disable-next-line no-bitwise
75
42
  hash = (hash * 33) ^ text.charCodeAt(i);
76
43
  }
77
44
 
78
45
  return Math.abs(hash);
79
46
  }
80
- }
47
+ }