-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTypeScriptSourceFile.ts
More file actions
112 lines (105 loc) · 3.64 KB
/
TypeScriptSourceFile.ts
File metadata and controls
112 lines (105 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { expandGlob } from "@std/fs";
import * as ts from "typescript";
import type { Extractions } from "./Extractions.ts";
import { escapeKey } from "../common/escapeKey.ts";
/**
* Represents a TypeScript source file and provides utilities for parsing and extracting
* internationalization (i18n) template strings.
*
* This class wraps a TypeScript source file and processes it to identify tagged template
* expressions (using `t` or `lt` tags) for translation purposes. It extracts these strings
* along with their file location information.
*
* @example
* ```typescript
* const sourceFile = new TypeScriptSourceFile(
* 'app.ts',
* 'const msg = t`Hello World`;',
* extractions
* );
* sourceFile.process();
* ```
*/
export class TypeScriptSourceFile {
constructor(
public fileName: string,
public content: string,
public extractions: Extractions,
) {}
/**
* Creates and returns a TypeScript SourceFile object from the current file's content.
*
* This method parses the file content using the TypeScript compiler API and creates
* a SourceFile AST representation with the latest ECMAScript target version.
*
* @returns {ts.SourceFile} A TypeScript SourceFile object representing the parsed content
* of the current file with parent references enabled.
*/
getSourceFile(): ts.SourceFile {
return ts.createSourceFile(
this.fileName,
this.content,
ts.ScriptTarget.Latest,
true,
);
}
/**
* Traverses and processes the current TypeScript source file, accumulating
* extraction results as it walks the AST.
*
* The traversal starts at the root node returned by `getSourceFile()` and is
* delegated to `processNode`.
*
* If no custom extraction collection is supplied, the instance's
* predefined `extractions` is used.
*
* @param extractions - Collection (e.g., array or map) that will be populated
* with extracted information during processing; defaults to the instance's
* `extractions`.
*/
process(extractions = this.extractions) {
this.processNode(this.getSourceFile(), extractions);
}
private processNode(node: ts.Node, extractions: Extractions) {
if (TypeScriptSourceFile.isTemplateString(node)) {
this.processTemplateString(node, this.getSourceFile(), extractions);
}
ts.forEachChild(node, (child) => this.processNode(child, extractions));
}
private static isTemplateString(
node: ts.Node,
): node is ts.TaggedTemplateExpression {
return ts.isTaggedTemplateExpression(node) &&
ts.isIdentifier(node.tag) &&
(node.tag.text === "t" || node.tag.text === "lt");
}
private processTemplateString(
node: ts.TaggedTemplateExpression,
sourceFile: ts.SourceFile,
extractions: Extractions,
) {
let templateString = "";
const template = node.template;
if (ts.isNoSubstitutionTemplateLiteral(template)) {
templateString = escapeKey(template.text);
} else if (ts.isTemplateExpression(template)) {
let combinedTemplateParts = escapeKey(template.head.text);
template.templateSpans.forEach((span, index) => {
combinedTemplateParts += "{" + index + "}" +
escapeKey(span.literal.text);
});
templateString = combinedTemplateParts;
}
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
extractions.addExtraction(templateString, this.fileName, line + 1);
}
static async glob(pattern: string, root: string): Promise<string[]> {
const filePaths: string[] = [];
for await (const file of expandGlob(pattern, { root })) {
if (file.isFile) {
filePaths.push(file.path);
}
}
return filePaths;
}
}