Skip to content

Commit

Permalink
feat: Generate exports file for JSON types and expose them in the typ…
Browse files Browse the repository at this point in the history
…es (#1578)
  • Loading branch information
Danielku15 authored Jul 13, 2024
1 parent a5a3afd commit d28966d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 63 deletions.
125 changes: 67 additions & 58 deletions src.compiler/typescript/EmitterBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,77 +9,86 @@ export const GENERATED_FILE_HEADER = `\
// the code is regenerated.
// </auto-generated>`;

export default function createEmitter(
jsDocMarker: string,
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
) {
function generateClass(program: ts.Program, classDeclaration: ts.ClassDeclaration) {
const sourceFileName = path.relative(
path.resolve(program.getCompilerOptions().baseUrl!, 'src'),
path.resolve(classDeclaration.getSourceFile().fileName)
);
export function generateFile(program: ts.Program, sourceFile: ts.SourceFile, fileName: string) {
const targetFileName = path.join(path.resolve(program.getCompilerOptions().baseUrl!), 'src/generated', fileName);

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

const targetFileName = path.join(
path.resolve(program.getCompilerOptions().baseUrl!),
'src/generated',
path.dirname(sourceFileName),
`${defaultClass.name!.text}.ts`
);
const fileHandle = fs.openSync(targetFileName, 'w');

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

const fileHandle = fs.openSync(targetFileName, 'w');
const printer = ts.createPrinter();
const source = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile);
const servicesHost: ts.LanguageServiceHost = {
getScriptFileNames: () => [targetFileName],
getScriptVersion: () => program.getSourceFiles()[0].languageVersion.toString(),
getScriptSnapshot: fileName => (fileName === targetFileName ? ts.ScriptSnapshot.fromString(source) : undefined),
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => program.getCompilerOptions(),
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
fileExists: fileName => fileName === targetFileName,
readFile: fileName => (fileName === targetFileName ? source : ''),
readDirectory: ts.sys.readDirectory,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories
};

fs.writeSync(fileHandle, `${GENERATED_FILE_HEADER}\n`);
const languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
const formattingChanges: ts.TextChange[] = languageService.getFormattingEditsForDocument(targetFileName, {
convertTabsToSpaces: true,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
indentStyle: ts.IndentStyle.Smart,
indentSize: 4,
tabSize: 4,
trimTrailingWhitespace: true
} as ts.FormatCodeSettings);
formattingChanges.sort((a, b) => b.span.start - a.span.start);

const printer = ts.createPrinter();
const source = printer.printNode(ts.EmitHint.Unspecified, result, result);
const servicesHost: ts.LanguageServiceHost = {
getScriptFileNames: () => [targetFileName],
getScriptVersion: () => result.languageVersion.toString(),
getScriptSnapshot: fileName =>
fileName === targetFileName ? ts.ScriptSnapshot.fromString(source) : undefined,
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => program.getCompilerOptions(),
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
fileExists: fileName => fileName === targetFileName,
readFile: fileName => (fileName === targetFileName ? source : ''),
readDirectory: ts.sys.readDirectory,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories
};
let finalText = source;
for (const {
span: { start, length },
newText
} of formattingChanges) {
finalText = `${finalText.slice(0, start)}${newText}${finalText.slice(start + length)}`;
}

const languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
const formattingChanges: ts.TextChange[] = languageService.getFormattingEditsForDocument(targetFileName, {
convertTabsToSpaces: true,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
indentStyle: ts.IndentStyle.Smart,
indentSize: 4,
tabSize: 4,
trimTrailingWhitespace: true
} as ts.FormatCodeSettings);
formattingChanges.sort((a, b) => b.span.start - a.span.start);
fs.writeSync(fileHandle, finalText);
fs.closeSync(fileHandle);
}

let finalText = source;
for (const { span: { start, length }, newText } of formattingChanges) {
finalText = `${finalText.slice(0, start)}${newText}${finalText.slice(start + length)}`;
}
export function generateClass(
program: ts.Program,
classDeclaration: ts.ClassDeclaration,
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
) {
const sourceFileName = path.relative(
path.resolve(program.getCompilerOptions().baseUrl!, 'src'),
path.resolve(classDeclaration.getSourceFile().fileName)
);

fs.writeSync(fileHandle, finalText);
fs.closeSync(fileHandle);
}
const result = generate(program, classDeclaration);
const defaultClass = result.statements.find(
stmt =>
(ts.isClassDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) &&
stmt.modifiers!.find(m => m.kind === ts.SyntaxKind.ExportKeyword)
) as ts.DeclarationStatement;

const targetFileName = path.join(path.dirname(sourceFileName), `${defaultClass.name!.text}.ts`);

generateFile(program, result, targetFileName);
}

export default function createEmitter(
jsDocMarker: string,
generate: (program: ts.Program, classDeclaration: ts.ClassDeclaration) => ts.SourceFile
) {
function scanSourceFile(program: ts.Program, sourceFile: ts.SourceFile) {
sourceFile.statements.forEach(stmt => {
if (ts.isClassDeclaration(stmt) && ts.getJSDocTags(stmt).some(t => t.tagName.text === jsDocMarker)) {
generateClass(program, stmt);
generateClass(program, stmt, generate);
}
});
}
Expand Down
34 changes: 30 additions & 4 deletions src.compiler/typescript/JsonDeclarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as path from 'path';
import * as url from 'url';
import * as ts from 'typescript';
import createEmitter from './EmitterBase';
import createEmitter, { generateFile } from './EmitterBase';
import {
cloneTypeNode,
getTypeWithNullableInfo,
Expand Down Expand Up @@ -147,7 +147,7 @@ function createJsonTypeNode(
return undefined;
}

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

for (const tag of additionalTags) {
if(!text.includes(tag)) {
if (!text.includes(tag)) {
text += `* ${tag}\n `;
}
}
Expand Down Expand Up @@ -208,7 +208,8 @@ function createJsonMembers(
.map(m => createJsonMember(program, m as ts.PropertyDeclaration, importer));
}

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

Expand Down Expand Up @@ -248,6 +249,7 @@ export default createEmitter('json_declaration', (program, input) => {
)
);

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

return sourceFile;
});
export default function emitWithIndex(program: ts.Program, _diagnostics: ts.Diagnostic[]) {
allJsonTypes = [];

emit(program, _diagnostics);

const statements = allJsonTypes.map(type =>
ts.factory.createExportDeclaration(
undefined,
true,
ts.factory.createNamedExports([
ts.factory.createExportSpecifier(false, undefined, ts.factory.createIdentifier(type))
]),
ts.factory.createStringLiteral(`./${type}`)
)
);

const sourceFile = ts.factory.createSourceFile(
[...statements],
ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
ts.NodeFlags.None
);

generateFile(program, sourceFile, 'json.ts');
}
2 changes: 1 addition & 1 deletion src/alphaTab.core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ export * as model from "./model";
export * as rendering from "./rendering";
export * as platform from "./platform";
export * as synth from "./synth";

export * as json from './generated/json'
14 changes: 14 additions & 0 deletions src/generated/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// <auto-generated>
// This code was auto-generated.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
export type { CoreSettingsJson } from "./CoreSettingsJson";
export type { DisplaySettingsJson } from "./DisplaySettingsJson";
export type { ImporterSettingsJson } from "./ImporterSettingsJson";
export type { NotationSettingsJson } from "./NotationSettingsJson";
export type { VibratoPlaybackSettingsJson } from "./VibratoPlaybackSettingsJson";
export type { SlidePlaybackSettingsJson } from "./SlidePlaybackSettingsJson";
export type { PlayerSettingsJson } from "./PlayerSettingsJson";
export type { RenderingResourcesJson } from "./RenderingResourcesJson";
export type { SettingsJson } from "./SettingsJson";

0 comments on commit d28966d

Please sign in to comment.