Skip to content

Commit d28966d

Browse files
authored
feat: Generate exports file for JSON types and expose them in the types (#1578)
1 parent a5a3afd commit d28966d

File tree

4 files changed

+112
-63
lines changed

4 files changed

+112
-63
lines changed

src.compiler/typescript/EmitterBase.ts

Lines changed: 67 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,77 +9,86 @@ export const GENERATED_FILE_HEADER = `\
99
// the code is regenerated.
1010
// </auto-generated>`;
1111

12-
export default function createEmitter(
13-
jsDocMarker: string,
14-
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
15-
) {
16-
function generateClass(program: ts.Program, classDeclaration: ts.ClassDeclaration) {
17-
const sourceFileName = path.relative(
18-
path.resolve(program.getCompilerOptions().baseUrl!, 'src'),
19-
path.resolve(classDeclaration.getSourceFile().fileName)
20-
);
12+
export function generateFile(program: ts.Program, sourceFile: ts.SourceFile, fileName: string) {
13+
const targetFileName = path.join(path.resolve(program.getCompilerOptions().baseUrl!), 'src/generated', fileName);
2114

22-
const result = generate(program, classDeclaration);
23-
const defaultClass = result.statements.find(
24-
stmt => (ts.isClassDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) && stmt.modifiers!.find(m => m.kind === ts.SyntaxKind.ExportKeyword)
25-
) as ts.DeclarationStatement;
15+
fs.mkdirSync(path.dirname(targetFileName), { recursive: true });
2616

27-
const targetFileName = path.join(
28-
path.resolve(program.getCompilerOptions().baseUrl!),
29-
'src/generated',
30-
path.dirname(sourceFileName),
31-
`${defaultClass.name!.text}.ts`
32-
);
17+
const fileHandle = fs.openSync(targetFileName, 'w');
3318

34-
fs.mkdirSync(path.dirname(targetFileName), { recursive: true });
19+
fs.writeSync(fileHandle, `${GENERATED_FILE_HEADER}\n`);
3520

36-
const fileHandle = fs.openSync(targetFileName, 'w');
21+
const printer = ts.createPrinter();
22+
const source = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile);
23+
const servicesHost: ts.LanguageServiceHost = {
24+
getScriptFileNames: () => [targetFileName],
25+
getScriptVersion: () => program.getSourceFiles()[0].languageVersion.toString(),
26+
getScriptSnapshot: fileName => (fileName === targetFileName ? ts.ScriptSnapshot.fromString(source) : undefined),
27+
getCurrentDirectory: () => process.cwd(),
28+
getCompilationSettings: () => program.getCompilerOptions(),
29+
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
30+
fileExists: fileName => fileName === targetFileName,
31+
readFile: fileName => (fileName === targetFileName ? source : ''),
32+
readDirectory: ts.sys.readDirectory,
33+
directoryExists: ts.sys.directoryExists,
34+
getDirectories: ts.sys.getDirectories
35+
};
3736

38-
fs.writeSync(fileHandle, `${GENERATED_FILE_HEADER}\n`);
37+
const languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
38+
const formattingChanges: ts.TextChange[] = languageService.getFormattingEditsForDocument(targetFileName, {
39+
convertTabsToSpaces: true,
40+
insertSpaceAfterCommaDelimiter: true,
41+
insertSpaceAfterKeywordsInControlFlowStatements: true,
42+
insertSpaceBeforeAndAfterBinaryOperators: true,
43+
indentStyle: ts.IndentStyle.Smart,
44+
indentSize: 4,
45+
tabSize: 4,
46+
trimTrailingWhitespace: true
47+
} as ts.FormatCodeSettings);
48+
formattingChanges.sort((a, b) => b.span.start - a.span.start);
3949

40-
const printer = ts.createPrinter();
41-
const source = printer.printNode(ts.EmitHint.Unspecified, result, result);
42-
const servicesHost: ts.LanguageServiceHost = {
43-
getScriptFileNames: () => [targetFileName],
44-
getScriptVersion: () => result.languageVersion.toString(),
45-
getScriptSnapshot: fileName =>
46-
fileName === targetFileName ? ts.ScriptSnapshot.fromString(source) : undefined,
47-
getCurrentDirectory: () => process.cwd(),
48-
getCompilationSettings: () => program.getCompilerOptions(),
49-
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
50-
fileExists: fileName => fileName === targetFileName,
51-
readFile: fileName => (fileName === targetFileName ? source : ''),
52-
readDirectory: ts.sys.readDirectory,
53-
directoryExists: ts.sys.directoryExists,
54-
getDirectories: ts.sys.getDirectories
55-
};
50+
let finalText = source;
51+
for (const {
52+
span: { start, length },
53+
newText
54+
} of formattingChanges) {
55+
finalText = `${finalText.slice(0, start)}${newText}${finalText.slice(start + length)}`;
56+
}
5657

57-
const languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
58-
const formattingChanges: ts.TextChange[] = languageService.getFormattingEditsForDocument(targetFileName, {
59-
convertTabsToSpaces: true,
60-
insertSpaceAfterCommaDelimiter: true,
61-
insertSpaceAfterKeywordsInControlFlowStatements: true,
62-
insertSpaceBeforeAndAfterBinaryOperators: true,
63-
indentStyle: ts.IndentStyle.Smart,
64-
indentSize: 4,
65-
tabSize: 4,
66-
trimTrailingWhitespace: true
67-
} as ts.FormatCodeSettings);
68-
formattingChanges.sort((a, b) => b.span.start - a.span.start);
58+
fs.writeSync(fileHandle, finalText);
59+
fs.closeSync(fileHandle);
60+
}
6961

70-
let finalText = source;
71-
for (const { span: { start, length }, newText } of formattingChanges) {
72-
finalText = `${finalText.slice(0, start)}${newText}${finalText.slice(start + length)}`;
73-
}
62+
export function generateClass(
63+
program: ts.Program,
64+
classDeclaration: ts.ClassDeclaration,
65+
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
66+
) {
67+
const sourceFileName = path.relative(
68+
path.resolve(program.getCompilerOptions().baseUrl!, 'src'),
69+
path.resolve(classDeclaration.getSourceFile().fileName)
70+
);
7471

75-
fs.writeSync(fileHandle, finalText);
76-
fs.closeSync(fileHandle);
77-
}
72+
const result = generate(program, classDeclaration);
73+
const defaultClass = result.statements.find(
74+
stmt =>
75+
(ts.isClassDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) &&
76+
stmt.modifiers!.find(m => m.kind === ts.SyntaxKind.ExportKeyword)
77+
) as ts.DeclarationStatement;
78+
79+
const targetFileName = path.join(path.dirname(sourceFileName), `${defaultClass.name!.text}.ts`);
80+
81+
generateFile(program, result, targetFileName);
82+
}
7883

84+
export default function createEmitter(
85+
jsDocMarker: string,
86+
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
87+
) {
7988
function scanSourceFile(program: ts.Program, sourceFile: ts.SourceFile) {
8089
sourceFile.statements.forEach(stmt => {
8190
if (ts.isClassDeclaration(stmt) && ts.getJSDocTags(stmt).some(t => t.tagName.text === jsDocMarker)) {
82-
generateClass(program, stmt);
91+
generateClass(program, stmt, generate);
8392
}
8493
});
8594
}

src.compiler/typescript/JsonDeclarationEmitter.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import * as path from 'path';
66
import * as url from 'url';
77
import * as ts from 'typescript';
8-
import createEmitter from './EmitterBase';
8+
import createEmitter, { generateFile } from './EmitterBase';
99
import {
1010
cloneTypeNode,
1111
getTypeWithNullableInfo,
@@ -147,7 +147,7 @@ function createJsonTypeNode(
147147
return undefined;
148148
}
149149

150-
function cloneJsDoc<T extends ts.Node>(node: T, source: ts.Node, additionalTags:string[]): T {
150+
function cloneJsDoc<T extends ts.Node>(node: T, source: ts.Node, additionalTags: string[]): T {
151151
const docs = ts
152152
.getJSDocCommentsAndTags(source)
153153
.filter(s => ts.isJSDoc(s))
@@ -163,7 +163,7 @@ function cloneJsDoc<T extends ts.Node>(node: T, source: ts.Node, additionalTags:
163163
.trimStart();
164164

165165
for (const tag of additionalTags) {
166-
if(!text.includes(tag)) {
166+
if (!text.includes(tag)) {
167167
text += `* ${tag}\n `;
168168
}
169169
}
@@ -208,7 +208,8 @@ function createJsonMembers(
208208
.map(m => createJsonMember(program, m as ts.PropertyDeclaration, importer));
209209
}
210210

211-
export default createEmitter('json_declaration', (program, input) => {
211+
let allJsonTypes: string[] = [];
212+
const emit = createEmitter('json_declaration', (program, input) => {
212213
console.log(`Writing JSON Type Declaration for ${input.name!.text}`);
213214
const statements: ts.Statement[] = [];
214215

@@ -248,6 +249,7 @@ export default createEmitter('json_declaration', (program, input) => {
248249
)
249250
);
250251

252+
allJsonTypes.push(input.name!.text + 'Json');
251253
const sourceFile = ts.factory.createSourceFile(
252254
[...statements],
253255
ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
@@ -256,3 +258,27 @@ export default createEmitter('json_declaration', (program, input) => {
256258

257259
return sourceFile;
258260
});
261+
export default function emitWithIndex(program: ts.Program, _diagnostics: ts.Diagnostic[]) {
262+
allJsonTypes = [];
263+
264+
emit(program, _diagnostics);
265+
266+
const statements = allJsonTypes.map(type =>
267+
ts.factory.createExportDeclaration(
268+
undefined,
269+
true,
270+
ts.factory.createNamedExports([
271+
ts.factory.createExportSpecifier(false, undefined, ts.factory.createIdentifier(type))
272+
]),
273+
ts.factory.createStringLiteral(`./${type}`)
274+
)
275+
);
276+
277+
const sourceFile = ts.factory.createSourceFile(
278+
[...statements],
279+
ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
280+
ts.NodeFlags.None
281+
);
282+
283+
generateFile(program, sourceFile, 'json.ts');
284+
}

src/alphaTab.core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ export * as model from "./model";
2828
export * as rendering from "./rendering";
2929
export * as platform from "./platform";
3030
export * as synth from "./synth";
31-
31+
export * as json from './generated/json'

src/generated/json.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// <auto-generated>
2+
// This code was auto-generated.
3+
// Changes to this file may cause incorrect behavior and will be lost if
4+
// the code is regenerated.
5+
// </auto-generated>
6+
export type { CoreSettingsJson } from "./CoreSettingsJson";
7+
export type { DisplaySettingsJson } from "./DisplaySettingsJson";
8+
export type { ImporterSettingsJson } from "./ImporterSettingsJson";
9+
export type { NotationSettingsJson } from "./NotationSettingsJson";
10+
export type { VibratoPlaybackSettingsJson } from "./VibratoPlaybackSettingsJson";
11+
export type { SlidePlaybackSettingsJson } from "./SlidePlaybackSettingsJson";
12+
export type { PlayerSettingsJson } from "./PlayerSettingsJson";
13+
export type { RenderingResourcesJson } from "./RenderingResourcesJson";
14+
export type { SettingsJson } from "./SettingsJson";

0 commit comments

Comments
 (0)