Skip to content

Commit 92385e9

Browse files
committed
Genericize the TypingsGenerator utility from @rushstack/localization-plugin.
1 parent 7f1d691 commit 92385e9

File tree

9 files changed

+287
-90
lines changed

9 files changed

+287
-90
lines changed

common/reviews/api/localization-plugin.api.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
```ts
66

77
import { loader } from 'webpack';
8+
import { StringValuesTypingsGenerator } from '@rushstack/typings-generator';
89
import { Terminal } from '@microsoft/node-core-library';
910
import * as Webpack from 'webpack';
1011

@@ -208,13 +209,9 @@ export class _LocFileParser {
208209
}
209210

210211
// @public
211-
export class TypingsGenerator {
212+
export class TypingsGenerator extends StringValuesTypingsGenerator {
212213
constructor(options: ITypingsGeneratorOptions);
213-
// (undocumented)
214-
generateTypings(): void;
215-
// (undocumented)
216-
runWatcher(): void;
217-
}
214+
}
218215

219216

220217
// (No @packageDocumentation comment for this package)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
## API Report File for "@rushstack/typings-generator"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import { Terminal } from '@microsoft/node-core-library';
8+
9+
// @public (undocumented)
10+
export interface IStringValuesTypingsGeneratorOptions extends ITypingsGeneratorOptions<IStringValueTypings> {
11+
// (undocumented)
12+
exportAsDefault?: boolean;
13+
}
14+
15+
// @public (undocumented)
16+
export interface IStringValueTyping {
17+
// (undocumented)
18+
comment?: string;
19+
// (undocumented)
20+
exportName: string;
21+
}
22+
23+
// @public (undocumented)
24+
export interface IStringValueTypings {
25+
// (undocumented)
26+
typings: IStringValueTyping[];
27+
}
28+
29+
// @public (undocumented)
30+
export interface ITypingsGeneratorOptions<TTypingsResult = string> {
31+
// (undocumented)
32+
fileExtensions: string[];
33+
// (undocumented)
34+
filesToIgnore?: string[];
35+
// (undocumented)
36+
generatedTsFolder: string;
37+
// (undocumented)
38+
parseAndGenerateTypings: (fileContents: string, filePath: string) => TTypingsResult;
39+
// (undocumented)
40+
srcFolder: string;
41+
// (undocumented)
42+
terminal?: Terminal;
43+
}
44+
45+
// @public
46+
export class StringValuesTypingsGenerator extends TypingsGenerator {
47+
constructor(options: IStringValuesTypingsGeneratorOptions);
48+
}
49+
50+
// @public
51+
export class TypingsGenerator {
52+
constructor(options: ITypingsGeneratorOptions);
53+
// (undocumented)
54+
generateTypings(): void;
55+
// (undocumented)
56+
protected _options: ITypingsGeneratorOptions;
57+
// (undocumented)
58+
runWatcher(): void;
59+
}
60+
61+
62+
// (No @packageDocumentation comment for this package)
63+
64+
```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { EOL } from 'os';
5+
6+
import {
7+
ITypingsGeneratorOptions,
8+
TypingsGenerator
9+
} from './TypingsGenerator';
10+
11+
/**
12+
* @public
13+
*/
14+
export interface IStringValueTyping {
15+
exportName: string;
16+
comment?: string;
17+
}
18+
19+
/**
20+
* @public
21+
*/
22+
export interface IStringValueTypings {
23+
typings: IStringValueTyping[];
24+
}
25+
26+
/**
27+
* @public
28+
*/
29+
export interface IStringValuesTypingsGeneratorOptions extends ITypingsGeneratorOptions<IStringValueTypings> {
30+
exportAsDefault?: boolean;
31+
}
32+
33+
const EXPORT_AS_DEFAULT_INTERFACE_NAME: string = 'IExport';
34+
35+
/**
36+
* This is a simple tool that generates .d.ts files for non-TS files that can be represented as
37+
* a simple set of named string exports.
38+
*
39+
* @public
40+
*/
41+
export class StringValuesTypingsGenerator extends TypingsGenerator {
42+
public constructor(options: IStringValuesTypingsGeneratorOptions) {
43+
super({
44+
...options,
45+
parseAndGenerateTypings: (fileContents: string, filePath: string) => {
46+
const stringValueTypings: IStringValueTypings = options.parseAndGenerateTypings(fileContents, filePath);
47+
48+
const outputLines: string[] = [];
49+
let indent: string = '';
50+
if (options.exportAsDefault) {
51+
outputLines.push(
52+
`export interface ${EXPORT_AS_DEFAULT_INTERFACE_NAME} {`
53+
);
54+
55+
indent = ' ';
56+
}
57+
58+
for (const stringValueTyping of stringValueTypings.typings) {
59+
const { exportName, comment } = stringValueTyping;
60+
61+
if (comment && comment.trim() !== '') {
62+
outputLines.push(
63+
`${indent}/**`,
64+
`${indent} * ${comment.replace(/\*\//g, '*\\/')}`,
65+
`${indent} */`
66+
);
67+
}
68+
69+
if (options.exportAsDefault) {
70+
outputLines.push(
71+
`${indent}${exportName}: string;`,
72+
''
73+
);
74+
} else {
75+
outputLines.push(
76+
`export declare const ${exportName}: string;`,
77+
''
78+
);
79+
}
80+
}
81+
82+
if (options.exportAsDefault) {
83+
outputLines.push(
84+
'}',
85+
'',
86+
`declare const strings: ${EXPORT_AS_DEFAULT_INTERFACE_NAME};`,
87+
'export default strings;'
88+
);
89+
}
90+
91+
return outputLines.join(EOL);
92+
}
93+
});
94+
}
95+
}

libraries/typings-generator/src/TypingsGenerator.ts

Lines changed: 51 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,33 @@ import {
55
FileSystem,
66
Terminal,
77
ConsoleTerminalProvider,
8-
Path
8+
Path,
9+
NewlineKind
910
} from '@microsoft/node-core-library';
1011
import * as glob from 'glob';
1112
import * as path from 'path';
1213
import { EOL } from 'os';
1314
import * as chokidar from 'chokidar';
1415

15-
import { ILocalizationFile } from './interfaces';
16-
import { ILoggerOptions } from './utilities/Logging';
17-
import { LocFileParser } from './utilities/LocFileParser';
18-
1916
/**
2017
* @public
2118
*/
22-
export interface ITypingsGeneratorOptions {
19+
export interface ITypingsGeneratorOptions<TTypingsResult = string> {
2320
srcFolder: string;
21+
fileExtensions: string[];
2422
generatedTsFolder: string;
23+
parseAndGenerateTypings: (fileContents: string, filePath: string) => TTypingsResult;
2524
terminal?: Terminal;
26-
exportAsDefault?: boolean;
2725
filesToIgnore?: string[];
2826
}
2927

3028
/**
31-
* This is a simple tool that generates .d.ts files for .loc.json and .resx files.
29+
* This is a simple tool that generates .d.ts files for non-TS files.
3230
*
3331
* @public
3432
*/
3533
export class TypingsGenerator {
36-
private _options: ITypingsGeneratorOptions;
37-
private _loggingOptions: ILoggerOptions;
34+
protected _options: ITypingsGeneratorOptions;
3835

3936
public constructor(options: ITypingsGeneratorOptions) {
4037
this._options = {
@@ -57,6 +54,10 @@ export class TypingsGenerator {
5754
throw new Error('generatedTsFolder must not be under srcFolder');
5855
}
5956

57+
if (!this._options.fileExtensions || this._options.fileExtensions.length === 0) {
58+
throw new Error('At least one file extension must be provided.');
59+
}
60+
6061
if (!this._options.filesToIgnore) {
6162
this._options.filesToIgnore = [];
6263
}
@@ -65,10 +66,7 @@ export class TypingsGenerator {
6566
this._options.terminal = new Terminal(new ConsoleTerminalProvider({ verboseEnabled: true }));
6667
}
6768

68-
this._loggingOptions = {
69-
writeError: this._options.terminal.writeErrorLine.bind(this._options.terminal),
70-
writeWarning: this._options.terminal.writeWarningLine.bind(this._options.terminal)
71-
};
69+
this._options.fileExtensions = this._normalizeFileExtensions(this._options.fileExtensions);
7270
}
7371

7472
public generateTypings(): void {
@@ -78,8 +76,8 @@ export class TypingsGenerator {
7876
return path.resolve(this._options.srcFolder, fileToIgnore);
7977
}));
8078

81-
const locFilePaths: string[] = glob.sync(
82-
path.join('**', '*+(.resx|.loc.json)'),
79+
const filePaths: string[] = glob.sync(
80+
path.join('**', `*+(${this._options.fileExtensions.join('|')})`),
8381
{
8482
cwd: this._options.srcFolder,
8583
absolute: true,
@@ -88,14 +86,14 @@ export class TypingsGenerator {
8886
}
8987
);
9088

91-
for (let locFilePath of locFilePaths) {
92-
locFilePath = path.resolve(this._options.srcFolder, locFilePath);
89+
for (let filePath of filePaths) {
90+
filePath = path.resolve(this._options.srcFolder, filePath);
9391

94-
if (filesToIgnore.has(locFilePath)) {
92+
if (filesToIgnore.has(filePath)) {
9593
continue;
9694
}
9795

98-
this._parseFileAndGenerateTypings(locFilePath);
96+
this._parseFileAndGenerateTypings(filePath);
9997
}
10098
}
10199

@@ -105,76 +103,40 @@ export class TypingsGenerator {
105103
const globBase: string = path.resolve(this._options.srcFolder, '**');
106104

107105
const watcher: chokidar.FSWatcher = chokidar.watch(
108-
[path.join(globBase, '*.loc.json'), path.join(globBase, '*.resx')]
106+
this._options.fileExtensions.map((fileExtension) => path.join(globBase, `*${fileExtension}`))
109107
);
110-
const boundGenerateTypingsFunction: (locFilePath: string) => void = this._parseFileAndGenerateTypings.bind(this);
108+
const boundGenerateTypingsFunction: (filePath: string) => void = this._parseFileAndGenerateTypings.bind(this);
111109
watcher.on('add', boundGenerateTypingsFunction);
112110
watcher.on('change', boundGenerateTypingsFunction);
113-
watcher.on('unlink', (locFilePath) => {
114-
const generatedTsFilePath: string = this._getTypingsFilePath(locFilePath);
111+
watcher.on('unlink', (filePath) => {
112+
const generatedTsFilePath: string = this._getTypingsFilePath(filePath);
115113
FileSystem.deleteFile(generatedTsFilePath);
116114
});
117115
}
118116

119117
private _parseFileAndGenerateTypings(locFilePath: string): void {
120-
const locFileData: ILocalizationFile = LocFileParser.parseLocFile({
121-
filePath: locFilePath,
122-
content: FileSystem.readFile(locFilePath),
123-
loggerOptions: this._loggingOptions
124-
});
125-
this._generateTypingsForLocFile(locFilePath, locFileData);
126-
}
118+
try {
119+
const fileContents: string = FileSystem.readFile(locFilePath);
120+
const typingsData: string = this._options.parseAndGenerateTypings(fileContents, locFilePath);
121+
const generatedTsFilePath: string = this._getTypingsFilePath(locFilePath);
127122

128-
private _generateTypingsForLocFile(locFilePath: string, locFileData: ILocalizationFile): void {
129-
const outputLines: string[] = [
130-
'// This file was generated by a tool. Modifying it will produce unexpected behavior',
131-
''
132-
];
123+
const prefixedTypingsData: string = [
124+
'// This file was generated by a tool. Modifying it will produce unexpected behavior',
125+
'',
126+
typingsData
127+
].join(EOL);
133128

134-
let indent: string = '';
135-
if (this._options.exportAsDefault) {
136-
outputLines.push(
137-
'export interface IStrings {'
129+
FileSystem.writeFile(
130+
generatedTsFilePath,
131+
prefixedTypingsData,
132+
{ ensureFolderExists: true, convertLineEndings: NewlineKind.OsDefault }
138133
);
139134

140-
indent = ' ';
141-
}
142-
143-
for (const stringName in locFileData) { // eslint-disable-line guard-for-in
144-
const { comment } = locFileData[stringName];
145-
146-
if (comment && comment.trim() !== '') {
147-
outputLines.push(
148-
`${indent}/**`,
149-
`${indent} * ${comment.replace(/\*\//g, '*\\/')}`,
150-
`${indent} */`
151-
);
152-
}
153-
154-
if (this._options.exportAsDefault) {
155-
outputLines.push(
156-
`${indent}${stringName}: string;`,
157-
''
158-
);
159-
} else {
160-
outputLines.push(
161-
`export declare const ${stringName}: string;`,
162-
''
163-
);
164-
}
165-
}
166-
167-
if (this._options.exportAsDefault) {
168-
outputLines.push(
169-
'}',
170-
'',
171-
'declare const strings: IStrings;',
172-
'export default strings;'
135+
} catch (e) {
136+
this._options.terminal!.writeError(
137+
`Error occurred parsing and generating typings for file "${locFilePath}": ${e}`
173138
);
174139
}
175-
176-
const generatedTsFilePath: string = this._getTypingsFilePath(locFilePath);
177-
FileSystem.writeFile(generatedTsFilePath, outputLines.join(EOL), { ensureFolderExists: true });
178140
}
179141

180142
private _getTypingsFilePath(locFilePath: string): string {
@@ -183,4 +145,17 @@ export class TypingsGenerator {
183145
path.relative(this._options.srcFolder, `${locFilePath}.d.ts`)
184146
);
185147
}
148+
149+
private _normalizeFileExtensions(fileExtensions: string[]): string[] {
150+
const result: string[] = [];
151+
for (const fileExtension of fileExtensions) {
152+
if (!fileExtension.startsWith('.')) {
153+
result.push(`.${fileExtension}`);
154+
} else {
155+
result.push(fileExtension);
156+
}
157+
}
158+
159+
return result;
160+
}
186161
}

0 commit comments

Comments
 (0)