-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
extraction.ts
129 lines (120 loc) · 5.04 KB
/
extraction.ts
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {Logger} from '@angular/compiler-cli/src/ngtsc/logging';
import {SourceFile, SourceFileLoader} from '@angular/compiler-cli/src/ngtsc/sourcemaps';
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
import {transformSync} from '@babel/core';
import {makeEs2015ExtractPlugin} from './source_files/es2015_extract_plugin';
import {makeEs5ExtractPlugin} from './source_files/es5_extract_plugin';
export interface ExtractionOptions {
basePath: AbsoluteFsPath;
useSourceMaps?: boolean;
localizeName?: string;
}
/**
* Extracts parsed messages from file contents, by parsing the contents as JavaScript
* and looking for occurrences of `$localize` in the source code.
*
* @publicApi used by CLI
*/
export class MessageExtractor {
private basePath: AbsoluteFsPath;
private useSourceMaps: boolean;
private localizeName: string;
private loader: SourceFileLoader;
constructor(
private fs: FileSystem, private logger: Logger,
{basePath, useSourceMaps = true, localizeName = '$localize'}: ExtractionOptions) {
this.basePath = basePath;
this.useSourceMaps = useSourceMaps;
this.localizeName = localizeName;
this.loader = new SourceFileLoader(this.fs, this.logger, {webpack: basePath});
}
extractMessages(
filename: string,
): ɵParsedMessage[] {
const messages: ɵParsedMessage[] = [];
const sourceCode = this.fs.readFile(this.fs.resolve(this.basePath, filename));
if (sourceCode.includes(this.localizeName)) {
// Only bother to parse the file if it contains a reference to `$localize`.
transformSync(sourceCode, {
sourceRoot: this.basePath,
filename,
plugins: [
makeEs2015ExtractPlugin(this.fs, messages, this.localizeName),
makeEs5ExtractPlugin(this.fs, messages, this.localizeName),
],
code: false,
ast: false
});
}
if (this.useSourceMaps) {
this.updateSourceLocations(filename, sourceCode, messages);
}
return messages;
}
/**
* Update the location of each message to point to the source-mapped original source location, if
* available.
*/
private updateSourceLocations(filename: string, contents: string, messages: ɵParsedMessage[]):
void {
const sourceFile =
this.loader.loadSourceFile(this.fs.resolve(this.basePath, filename), contents);
if (sourceFile === null) {
return;
}
for (const message of messages) {
if (message.location !== undefined) {
message.location = this.getOriginalLocation(sourceFile, message.location);
if (message.messagePartLocations) {
message.messagePartLocations = message.messagePartLocations.map(
location => location && this.getOriginalLocation(sourceFile, location));
}
if (message.substitutionLocations) {
const placeholderNames = Object.keys(message.substitutionLocations);
for (const placeholderName of placeholderNames) {
const location = message.substitutionLocations[placeholderName];
message.substitutionLocations[placeholderName] =
location && this.getOriginalLocation(sourceFile, location);
}
}
}
}
}
/**
* Find the original location using source-maps if available.
*
* @param sourceFile The generated `sourceFile` that contains the `location`.
* @param location The location within the generated `sourceFile` that needs mapping.
*
* @returns A new location that refers to the original source location mapped from the given
* `location` in the generated `sourceFile`.
*/
private getOriginalLocation(sourceFile: SourceFile, location: ɵSourceLocation): ɵSourceLocation {
const originalStart =
sourceFile.getOriginalLocation(location.start.line, location.start.column);
if (originalStart === null) {
return location;
}
const originalEnd = sourceFile.getOriginalLocation(location.end.line, location.end.column);
const start = {line: originalStart.line, column: originalStart.column};
// We check whether the files are the same, since the returned location can only have a single
// `file` and it would not make sense to store the end position from a different source file.
const end = (originalEnd !== null && originalEnd.file === originalStart.file) ?
{line: originalEnd.line, column: originalEnd.column} :
start;
const originalSourceFile =
sourceFile.sources.find(sf => sf?.sourcePath === originalStart.file)!;
const startPos = originalSourceFile.startOfLinePositions[start.line] + start.column;
const endPos = originalSourceFile.startOfLinePositions[end.line] + end.column;
const text = originalSourceFile.contents.substring(startPos, endPos);
return {file: originalStart.file, start, end, text};
}
}