refactor(localize): avoid free-standing FileSystem
functions (#39006)
These free standing functions rely upon the "current" `FileSystem`, but it is safer to explicitly pass the `FileSystem` into functions or classes that need it. Fixes #38711 PR Close #39006
This commit is contained in:
parent
669e07580c
commit
1b5f6ee7a6
@ -52,8 +52,8 @@ export class MessageExtractor {
|
|||||||
sourceRoot: this.basePath,
|
sourceRoot: this.basePath,
|
||||||
filename,
|
filename,
|
||||||
plugins: [
|
plugins: [
|
||||||
makeEs2015ExtractPlugin(messages, this.localizeName),
|
makeEs2015ExtractPlugin(this.fs, messages, this.localizeName),
|
||||||
makeEs5ExtractPlugin(messages, this.localizeName),
|
makeEs5ExtractPlugin(this.fs, messages, this.localizeName),
|
||||||
],
|
],
|
||||||
code: false,
|
code: false,
|
||||||
ast: false
|
ast: false
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {getFileSystem, setFileSystem, NodeJSFileSystem, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {setFileSystem, NodeJSFileSystem, AbsoluteFsPath, FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ConsoleLogger, Logger, LogLevel} from '@angular/compiler-cli/src/ngtsc/logging';
|
import {ConsoleLogger, Logger, LogLevel} from '@angular/compiler-cli/src/ngtsc/logging';
|
||||||
import {ɵParsedMessage} from '@angular/localize';
|
import {ɵParsedMessage} from '@angular/localize';
|
||||||
import * as glob from 'glob';
|
import * as glob from 'glob';
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
import {DiagnosticHandlingStrategy, Diagnostics} from '../diagnostics';
|
import {DiagnosticHandlingStrategy} from '../diagnostics';
|
||||||
|
|
||||||
import {checkDuplicateMessages} from './duplicates';
|
import {checkDuplicateMessages} from './duplicates';
|
||||||
import {MessageExtractor} from './extraction';
|
import {MessageExtractor} from './extraction';
|
||||||
@ -97,8 +97,8 @@ if (require.main === module) {
|
|||||||
.help()
|
.help()
|
||||||
.parse(args);
|
.parse(args);
|
||||||
|
|
||||||
const fs = new NodeJSFileSystem();
|
const fileSystem = new NodeJSFileSystem();
|
||||||
setFileSystem(fs);
|
setFileSystem(fileSystem);
|
||||||
|
|
||||||
const rootPath = options.r;
|
const rootPath = options.r;
|
||||||
const sourceFilePaths = glob.sync(options.s, {cwd: rootPath, nodir: true});
|
const sourceFilePaths = glob.sync(options.s, {cwd: rootPath, nodir: true});
|
||||||
@ -119,6 +119,7 @@ if (require.main === module) {
|
|||||||
useLegacyIds: options.useLegacyIds,
|
useLegacyIds: options.useLegacyIds,
|
||||||
duplicateMessageHandling,
|
duplicateMessageHandling,
|
||||||
formatOptions,
|
formatOptions,
|
||||||
|
fileSystem,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +167,10 @@ export interface ExtractTranslationsOptions {
|
|||||||
* A collection of formatting options to pass to the translation file serializer.
|
* A collection of formatting options to pass to the translation file serializer.
|
||||||
*/
|
*/
|
||||||
formatOptions?: FormatOptions;
|
formatOptions?: FormatOptions;
|
||||||
|
/**
|
||||||
|
* The file-system abstraction to use.
|
||||||
|
*/
|
||||||
|
fileSystem: FileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractTranslations({
|
export function extractTranslations({
|
||||||
@ -179,8 +184,8 @@ export function extractTranslations({
|
|||||||
useLegacyIds,
|
useLegacyIds,
|
||||||
duplicateMessageHandling,
|
duplicateMessageHandling,
|
||||||
formatOptions = {},
|
formatOptions = {},
|
||||||
|
fileSystem: fs,
|
||||||
}: ExtractTranslationsOptions) {
|
}: ExtractTranslationsOptions) {
|
||||||
const fs = getFileSystem();
|
|
||||||
const basePath = fs.resolve(rootPath);
|
const basePath = fs.resolve(rootPath);
|
||||||
const extractor = new MessageExtractor(fs, logger, {basePath, useSourceMaps});
|
const extractor = new MessageExtractor(fs, logger, {basePath, useSourceMaps});
|
||||||
|
|
||||||
@ -196,7 +201,7 @@ export function extractTranslations({
|
|||||||
|
|
||||||
const outputPath = fs.resolve(rootPath, output);
|
const outputPath = fs.resolve(rootPath, output);
|
||||||
const serializer =
|
const serializer =
|
||||||
getSerializer(format, sourceLocale, fs.dirname(outputPath), useLegacyIds, formatOptions);
|
getSerializer(format, sourceLocale, fs.dirname(outputPath), useLegacyIds, formatOptions, fs);
|
||||||
const translationFile = serializer.serialize(messages);
|
const translationFile = serializer.serialize(messages);
|
||||||
fs.ensureDir(fs.dirname(outputPath));
|
fs.ensureDir(fs.dirname(outputPath));
|
||||||
fs.writeFile(outputPath, translationFile);
|
fs.writeFile(outputPath, translationFile);
|
||||||
@ -208,18 +213,20 @@ export function extractTranslations({
|
|||||||
|
|
||||||
export function getSerializer(
|
export function getSerializer(
|
||||||
format: string, sourceLocale: string, rootPath: AbsoluteFsPath, useLegacyIds: boolean,
|
format: string, sourceLocale: string, rootPath: AbsoluteFsPath, useLegacyIds: boolean,
|
||||||
formatOptions: FormatOptions = {}): TranslationSerializer {
|
formatOptions: FormatOptions = {}, fs: FileSystem): TranslationSerializer {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'xlf':
|
case 'xlf':
|
||||||
case 'xlif':
|
case 'xlif':
|
||||||
case 'xliff':
|
case 'xliff':
|
||||||
return new Xliff1TranslationSerializer(sourceLocale, rootPath, useLegacyIds, formatOptions);
|
return new Xliff1TranslationSerializer(
|
||||||
|
sourceLocale, rootPath, useLegacyIds, formatOptions, fs);
|
||||||
case 'xlf2':
|
case 'xlf2':
|
||||||
case 'xlif2':
|
case 'xlif2':
|
||||||
case 'xliff2':
|
case 'xliff2':
|
||||||
return new Xliff2TranslationSerializer(sourceLocale, rootPath, useLegacyIds, formatOptions);
|
return new Xliff2TranslationSerializer(
|
||||||
|
sourceLocale, rootPath, useLegacyIds, formatOptions, fs);
|
||||||
case 'xmb':
|
case 'xmb':
|
||||||
return new XmbTranslationSerializer(rootPath, useLegacyIds);
|
return new XmbTranslationSerializer(rootPath, useLegacyIds, fs);
|
||||||
case 'json':
|
case 'json':
|
||||||
return new SimpleJsonTranslationSerializer(sourceLocale);
|
return new SimpleJsonTranslationSerializer(sourceLocale);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
|
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
|
||||||
import {NodePath, PluginObj} from '@babel/core';
|
import {NodePath, PluginObj} from '@babel/core';
|
||||||
import {TaggedTemplateExpression} from '@babel/types';
|
import {TaggedTemplateExpression} from '@babel/types';
|
||||||
@ -12,7 +13,7 @@ import {TaggedTemplateExpression} from '@babel/types';
|
|||||||
import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapExpressionsFromTemplateLiteral, unwrapMessagePartsFromTemplateLiteral} from '../../source_file_utils';
|
import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapExpressionsFromTemplateLiteral, unwrapMessagePartsFromTemplateLiteral} from '../../source_file_utils';
|
||||||
|
|
||||||
export function makeEs2015ExtractPlugin(
|
export function makeEs2015ExtractPlugin(
|
||||||
messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
fs: FileSystem, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
|
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
|
||||||
@ -20,10 +21,10 @@ export function makeEs2015ExtractPlugin(
|
|||||||
if (isNamedIdentifier(tag, localizeName) && isGlobalIdentifier(tag)) {
|
if (isNamedIdentifier(tag, localizeName) && isGlobalIdentifier(tag)) {
|
||||||
const quasiPath = path.get('quasi');
|
const quasiPath = path.get('quasi');
|
||||||
const [messageParts, messagePartLocations] =
|
const [messageParts, messagePartLocations] =
|
||||||
unwrapMessagePartsFromTemplateLiteral(quasiPath.get('quasis'));
|
unwrapMessagePartsFromTemplateLiteral(quasiPath.get('quasis'), fs);
|
||||||
const [expressions, expressionLocations] =
|
const [expressions, expressionLocations] =
|
||||||
unwrapExpressionsFromTemplateLiteral(quasiPath);
|
unwrapExpressionsFromTemplateLiteral(quasiPath, fs);
|
||||||
const location = getLocation(quasiPath);
|
const location = getLocation(fs, quasiPath);
|
||||||
const message = ɵparseMessage(
|
const message = ɵparseMessage(
|
||||||
messageParts, expressions, location, messagePartLocations, expressionLocations);
|
messageParts, expressions, location, messagePartLocations, expressionLocations);
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
|
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
|
||||||
import {NodePath, PluginObj} from '@babel/core';
|
import {NodePath, PluginObj} from '@babel/core';
|
||||||
import {CallExpression} from '@babel/types';
|
import {CallExpression} from '@babel/types';
|
||||||
@ -12,16 +13,18 @@ import {CallExpression} from '@babel/types';
|
|||||||
import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapMessagePartsFromLocalizeCall, unwrapSubstitutionsFromLocalizeCall} from '../../source_file_utils';
|
import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapMessagePartsFromLocalizeCall, unwrapSubstitutionsFromLocalizeCall} from '../../source_file_utils';
|
||||||
|
|
||||||
export function makeEs5ExtractPlugin(
|
export function makeEs5ExtractPlugin(
|
||||||
messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
fs: FileSystem, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
CallExpression(callPath: NodePath<CallExpression>) {
|
CallExpression(callPath: NodePath<CallExpression>) {
|
||||||
const calleePath = callPath.get('callee');
|
const calleePath = callPath.get('callee');
|
||||||
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
||||||
const [messageParts, messagePartLocations] = unwrapMessagePartsFromLocalizeCall(callPath);
|
const [messageParts, messagePartLocations] =
|
||||||
const [expressions, expressionLocations] = unwrapSubstitutionsFromLocalizeCall(callPath);
|
unwrapMessagePartsFromLocalizeCall(callPath, fs);
|
||||||
|
const [expressions, expressionLocations] =
|
||||||
|
unwrapSubstitutionsFromLocalizeCall(callPath, fs);
|
||||||
const [messagePartsArg, expressionsArg] = callPath.get('arguments');
|
const [messagePartsArg, expressionsArg] = callPath.get('arguments');
|
||||||
const location = getLocation(messagePartsArg, expressionsArg);
|
const location = getLocation(fs, messagePartsArg, expressionsArg);
|
||||||
const message = ɵparseMessage(
|
const message = ɵparseMessage(
|
||||||
messageParts, expressions, location, messagePartLocations, expressionLocations);
|
messageParts, expressions, location, messagePartLocations, expressionLocations);
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AbsoluteFsPath, relative} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
||||||
|
|
||||||
import {FormatOptions, validateOptions} from './format_options';
|
import {FormatOptions, validateOptions} from './format_options';
|
||||||
@ -28,7 +28,7 @@ const LEGACY_XLIFF_MESSAGE_LENGTH = 40;
|
|||||||
export class Xliff1TranslationSerializer implements TranslationSerializer {
|
export class Xliff1TranslationSerializer implements TranslationSerializer {
|
||||||
constructor(
|
constructor(
|
||||||
private sourceLocale: string, private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
|
private sourceLocale: string, private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
|
||||||
private formatOptions: FormatOptions = {}) {
|
private formatOptions: FormatOptions = {}, private fs: FileSystem = getFileSystem()) {
|
||||||
validateOptions('Xliff1TranslationSerializer', [['xml:space', ['preserve']]], formatOptions);
|
validateOptions('Xliff1TranslationSerializer', [['xml:space', ['preserve']]], formatOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ export class Xliff1TranslationSerializer implements TranslationSerializer {
|
|||||||
|
|
||||||
private serializeLocation(xml: XmlFile, location: ɵSourceLocation): void {
|
private serializeLocation(xml: XmlFile, location: ɵSourceLocation): void {
|
||||||
xml.startTag('context-group', {purpose: 'location'});
|
xml.startTag('context-group', {purpose: 'location'});
|
||||||
this.renderContext(xml, 'sourcefile', relative(this.basePath, location.file));
|
this.renderContext(xml, 'sourcefile', this.fs.relative(this.basePath, location.file));
|
||||||
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
|
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
|
||||||
`,${location.end.line + 1}` :
|
`,${location.end.line + 1}` :
|
||||||
'';
|
'';
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AbsoluteFsPath, relative} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
||||||
|
|
||||||
import {FormatOptions, validateOptions} from './format_options';
|
import {FormatOptions, validateOptions} from './format_options';
|
||||||
@ -28,7 +28,7 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
|
|||||||
private currentPlaceholderId = 0;
|
private currentPlaceholderId = 0;
|
||||||
constructor(
|
constructor(
|
||||||
private sourceLocale: string, private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
|
private sourceLocale: string, private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
|
||||||
private formatOptions: FormatOptions = {}) {
|
private formatOptions: FormatOptions = {}, private fs: FileSystem = getFileSystem()) {
|
||||||
validateOptions('Xliff1TranslationSerializer', [['xml:space', ['preserve']]], formatOptions);
|
validateOptions('Xliff1TranslationSerializer', [['xml:space', ['preserve']]], formatOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
|
|||||||
end !== undefined && end.line !== start.line ? `,${end.line + 1}` : '';
|
end !== undefined && end.line !== start.line ? `,${end.line + 1}` : '';
|
||||||
this.serializeNote(
|
this.serializeNote(
|
||||||
xml, 'location',
|
xml, 'location',
|
||||||
`${relative(this.basePath, file)}:${start.line + 1}${endLineString}`);
|
`${this.fs.relative(this.basePath, file)}:${start.line + 1}${endLineString}`);
|
||||||
}
|
}
|
||||||
if (message.description) {
|
if (message.description) {
|
||||||
this.serializeNote(xml, 'description', message.description);
|
this.serializeNote(xml, 'description', message.description);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AbsoluteFsPath, relative} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
||||||
|
|
||||||
import {extractIcuPlaceholders} from './icu_parsing';
|
import {extractIcuPlaceholders} from './icu_parsing';
|
||||||
@ -21,7 +21,9 @@ import {XmlFile} from './xml_file';
|
|||||||
* @publicApi used by CLI
|
* @publicApi used by CLI
|
||||||
*/
|
*/
|
||||||
export class XmbTranslationSerializer implements TranslationSerializer {
|
export class XmbTranslationSerializer implements TranslationSerializer {
|
||||||
constructor(private basePath: AbsoluteFsPath, private useLegacyIds: boolean) {}
|
constructor(
|
||||||
|
private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
|
||||||
|
private fs: FileSystem = getFileSystem()) {}
|
||||||
|
|
||||||
serialize(messages: ɵParsedMessage[]): string {
|
serialize(messages: ɵParsedMessage[]): string {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
@ -74,7 +76,8 @@ export class XmbTranslationSerializer implements TranslationSerializer {
|
|||||||
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
|
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
|
||||||
`,${location.end.line + 1}` :
|
`,${location.end.line + 1}` :
|
||||||
'';
|
'';
|
||||||
xml.text(`${relative(this.basePath, location.file)}:${location.start.line}${endLineString}`);
|
xml.text(
|
||||||
|
`${this.fs.relative(this.basePath, location.file)}:${location.start.line}${endLineString}`);
|
||||||
xml.endTag('source');
|
xml.endTag('source');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AbsoluteFsPath, relative, resolve} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵisMissingTranslationError, ɵmakeTemplateObject, ɵParsedTranslation, ɵSourceLocation, ɵtranslate} from '@angular/localize';
|
import {ɵisMissingTranslationError, ɵmakeTemplateObject, ɵParsedTranslation, ɵSourceLocation, ɵtranslate} from '@angular/localize';
|
||||||
import {NodePath} from '@babel/traverse';
|
import {NodePath} from '@babel/traverse';
|
||||||
import * as t from '@babel/types';
|
import * as t from '@babel/types';
|
||||||
@ -68,10 +68,14 @@ export function buildLocalizeReplacement(
|
|||||||
* to a helper function like `__makeTemplateObject`.
|
* to a helper function like `__makeTemplateObject`.
|
||||||
*
|
*
|
||||||
* @param call The AST node of the call to process.
|
* @param call The AST node of the call to process.
|
||||||
|
* @param fs The file system to use when computing source-map paths. If not provided then it uses
|
||||||
|
* the "current" FileSystem.
|
||||||
* @publicApi used by CLI
|
* @publicApi used by CLI
|
||||||
*/
|
*/
|
||||||
export function unwrapMessagePartsFromLocalizeCall(call: NodePath<t.CallExpression>):
|
export function unwrapMessagePartsFromLocalizeCall(
|
||||||
[TemplateStringsArray, (ɵSourceLocation | undefined)[]] {
|
call: NodePath<t.CallExpression>,
|
||||||
|
fs: FileSystem = getFileSystem(),
|
||||||
|
): [TemplateStringsArray, (ɵSourceLocation | undefined)[]] {
|
||||||
let cooked = call.get('arguments')[0];
|
let cooked = call.get('arguments')[0];
|
||||||
|
|
||||||
if (cooked === undefined) {
|
if (cooked === undefined) {
|
||||||
@ -141,16 +145,22 @@ export function unwrapMessagePartsFromLocalizeCall(call: NodePath<t.CallExpressi
|
|||||||
raw = arg2 !== undefined ? arg2 : cooked;
|
raw = arg2 !== undefined ? arg2 : cooked;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [cookedStrings] = unwrapStringLiteralArray(cooked);
|
const [cookedStrings] = unwrapStringLiteralArray(cooked, fs);
|
||||||
const [rawStrings, rawLocations] = unwrapStringLiteralArray(raw);
|
const [rawStrings, rawLocations] = unwrapStringLiteralArray(raw, fs);
|
||||||
return [ɵmakeTemplateObject(cookedStrings, rawStrings), rawLocations];
|
return [ɵmakeTemplateObject(cookedStrings, rawStrings), rawLocations];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the localize call expression to extract the arguments that hold the substition expressions.
|
* Parse the localize call expression to extract the arguments that hold the substition expressions.
|
||||||
*
|
*
|
||||||
|
* @param call The AST node of the call to process.
|
||||||
|
* @param fs The file system to use when computing source-map paths. If not provided then it uses
|
||||||
|
* the "current" FileSystem.
|
||||||
* @publicApi used by CLI
|
* @publicApi used by CLI
|
||||||
*/
|
*/
|
||||||
|
export function unwrapSubstitutionsFromLocalizeCall(
|
||||||
|
call: NodePath<t.CallExpression>,
|
||||||
|
fs: FileSystem = getFileSystem()): [t.Expression[], (ɵSourceLocation | undefined)[]] {
|
||||||
const expressions = call.get('arguments').splice(1);
|
const expressions = call.get('arguments').splice(1);
|
||||||
if (!isArrayOfExpressions(expressions)) {
|
if (!isArrayOfExpressions(expressions)) {
|
||||||
const badExpression = expressions.find(expression => !expression.isExpression())!;
|
const badExpression = expressions.find(expression => !expression.isExpression())!;
|
||||||
@ -159,15 +169,21 @@ export function unwrapMessagePartsFromLocalizeCall(call: NodePath<t.CallExpressi
|
|||||||
'Invalid substitutions for `$localize` (expected all substitution arguments to be expressions).');
|
'Invalid substitutions for `$localize` (expected all substitution arguments to be expressions).');
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
expressions.map(path => path.node), expressions.map(expression => getLocation(expression))
|
expressions.map(path => path.node), expressions.map(expression => getLocation(fs, expression))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the tagged template literal to extract the message parts.
|
* Parse the tagged template literal to extract the message parts.
|
||||||
*
|
*
|
||||||
|
* @param elements The elements of the template literal to process.
|
||||||
|
* @param fs The file system to use when computing source-map paths. If not provided then it uses
|
||||||
|
* the "current" FileSystem.
|
||||||
* @publicApi used by CLI
|
* @publicApi used by CLI
|
||||||
*/
|
*/
|
||||||
|
export function unwrapMessagePartsFromTemplateLiteral(
|
||||||
|
elements: NodePath<t.TemplateElement>[],
|
||||||
|
fs: FileSystem = getFileSystem()): [TemplateStringsArray, (ɵSourceLocation | undefined)[]] {
|
||||||
const cooked = elements.map(q => {
|
const cooked = elements.map(q => {
|
||||||
if (q.node.value.cooked === undefined) {
|
if (q.node.value.cooked === undefined) {
|
||||||
throw new BabelParseError(
|
throw new BabelParseError(
|
||||||
@ -177,15 +193,22 @@ export function unwrapMessagePartsFromLocalizeCall(call: NodePath<t.CallExpressi
|
|||||||
return q.node.value.cooked;
|
return q.node.value.cooked;
|
||||||
});
|
});
|
||||||
const raw = elements.map(q => q.node.value.raw);
|
const raw = elements.map(q => q.node.value.raw);
|
||||||
const locations = elements.map(q => getLocation(q));
|
const locations = elements.map(q => getLocation(fs, q));
|
||||||
return [ɵmakeTemplateObject(cooked, raw), locations];
|
return [ɵmakeTemplateObject(cooked, raw), locations];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the tagged template literal to extract the interpolation expressions.
|
* Parse the tagged template literal to extract the interpolation expressions.
|
||||||
*
|
*
|
||||||
|
* @param quasi The AST node of the template literal to process.
|
||||||
|
* @param fs The file system to use when computing source-map paths. If not provided then it uses
|
||||||
|
* the "current" FileSystem.
|
||||||
* @publicApi used by CLI
|
* @publicApi used by CLI
|
||||||
*/
|
*/
|
||||||
|
export function unwrapExpressionsFromTemplateLiteral(
|
||||||
|
quasi: NodePath<t.TemplateLiteral>,
|
||||||
|
fs: FileSystem = getFileSystem()): [t.Expression[], (ɵSourceLocation | undefined)[]] {
|
||||||
|
return [quasi.node.expressions, quasi.get('expressions').map(e => getLocation(fs, e))];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,16 +228,20 @@ export function wrapInParensIfNecessary(expression: t.Expression): t.Expression
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the string values from an `array` of string literals.
|
* Extract the string values from an `array` of string literals.
|
||||||
|
*
|
||||||
* @param array The array to unwrap.
|
* @param array The array to unwrap.
|
||||||
|
* @param fs The file system to use when computing source-map paths. If not provided then it uses
|
||||||
|
* the "current" FileSystem.
|
||||||
*/
|
*/
|
||||||
export function unwrapStringLiteralArray(array: NodePath<t.Expression>):
|
export function unwrapStringLiteralArray(
|
||||||
[string[], (ɵSourceLocation | undefined)[]] {
|
array: NodePath<t.Expression>,
|
||||||
|
fs: FileSystem = getFileSystem()): [string[], (ɵSourceLocation | undefined)[]] {
|
||||||
if (!isStringLiteralArray(array.node)) {
|
if (!isStringLiteralArray(array.node)) {
|
||||||
throw new BabelParseError(
|
throw new BabelParseError(
|
||||||
array.node, 'Unexpected messageParts for `$localize` (expected an array of strings).');
|
array.node, 'Unexpected messageParts for `$localize` (expected an array of strings).');
|
||||||
}
|
}
|
||||||
const elements = array.get('elements') as NodePath<t.StringLiteral>[];
|
const elements = array.get('elements') as NodePath<t.StringLiteral>[];
|
||||||
return [elements.map(str => str.node.value), elements.map(str => getLocation(str))];
|
return [elements.map(str => str.node.value), elements.map(str => getLocation(fs, str))];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,15 +399,16 @@ export function buildCodeFrameError(path: NodePath, e: BabelParseError): string
|
|||||||
return `${filename}: ${message}`;
|
return `${filename}: ${message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocation(startPath: NodePath, endPath?: NodePath): ɵSourceLocation|undefined {
|
export function getLocation(
|
||||||
|
fs: FileSystem, startPath: NodePath, endPath?: NodePath): ɵSourceLocation|undefined {
|
||||||
const startLocation = startPath.node.loc;
|
const startLocation = startPath.node.loc;
|
||||||
const file = getFileFromPath(startPath);
|
const file = getFileFromPath(fs, startPath);
|
||||||
if (!startLocation || !file) {
|
if (!startLocation || !file) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endLocation =
|
const endLocation =
|
||||||
endPath && getFileFromPath(endPath) === file && endPath.node.loc || startLocation;
|
endPath && getFileFromPath(fs, endPath) === file && endPath.node.loc || startLocation;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: getLineAndColumn(startLocation.start),
|
start: getLineAndColumn(startLocation.start),
|
||||||
@ -397,10 +425,10 @@ export function serializeLocationPosition(location: ɵSourceLocation): string {
|
|||||||
return `${location.start.line + 1}${endLineString}`;
|
return `${location.start.line + 1}${endLineString}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileFromPath(path: NodePath|undefined): AbsoluteFsPath|null {
|
function getFileFromPath(fs: FileSystem, path: NodePath|undefined): AbsoluteFsPath|null {
|
||||||
const opts = path?.hub.file.opts;
|
const opts = path?.hub.file.opts;
|
||||||
return opts?.filename ?
|
return opts?.filename ?
|
||||||
resolve(opts.generatorOpts.sourceRoot ?? opts.cwd, relative(opts.cwd, opts.filename)) :
|
fs.resolve(opts.generatorOpts.sourceRoot ?? opts.cwd, fs.relative(opts.cwd, opts.filename)) :
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedTranslation} from '@angular/localize';
|
import {ɵParsedTranslation} from '@angular/localize';
|
||||||
import {NodePath, PluginObj} from '@babel/core';
|
import {NodePath, PluginObj} from '@babel/core';
|
||||||
import {TaggedTemplateExpression} from '@babel/types';
|
import {TaggedTemplateExpression} from '@babel/types';
|
||||||
@ -21,8 +22,8 @@ import {buildCodeFrameError, buildLocalizeReplacement, isBabelParseError, isLoca
|
|||||||
*/
|
*/
|
||||||
export function makeEs2015TranslatePlugin(
|
export function makeEs2015TranslatePlugin(
|
||||||
diagnostics: Diagnostics, translations: Record<string, ɵParsedTranslation>,
|
diagnostics: Diagnostics, translations: Record<string, ɵParsedTranslation>,
|
||||||
{missingTranslation = 'error', localizeName = '$localize'}: TranslatePluginOptions = {}):
|
{missingTranslation = 'error', localizeName = '$localize'}: TranslatePluginOptions = {},
|
||||||
PluginObj {
|
fs: FileSystem = getFileSystem()): PluginObj {
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
|
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
|
||||||
@ -30,7 +31,7 @@ export function makeEs2015TranslatePlugin(
|
|||||||
const tag = path.get('tag');
|
const tag = path.get('tag');
|
||||||
if (isLocalize(tag, localizeName)) {
|
if (isLocalize(tag, localizeName)) {
|
||||||
const [messageParts] =
|
const [messageParts] =
|
||||||
unwrapMessagePartsFromTemplateLiteral(path.get('quasi').get('quasis'));
|
unwrapMessagePartsFromTemplateLiteral(path.get('quasi').get('quasis'), fs);
|
||||||
const translated = translate(
|
const translated = translate(
|
||||||
diagnostics, translations, messageParts, path.node.quasi.expressions,
|
diagnostics, translations, messageParts, path.node.quasi.expressions,
|
||||||
missingTranslation);
|
missingTranslation);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {ɵParsedTranslation} from '@angular/localize';
|
import {ɵParsedTranslation} from '@angular/localize';
|
||||||
import {NodePath, PluginObj} from '@babel/core';
|
import {NodePath, PluginObj} from '@babel/core';
|
||||||
import {CallExpression} from '@babel/types';
|
import {CallExpression} from '@babel/types';
|
||||||
@ -21,16 +22,16 @@ import {buildCodeFrameError, buildLocalizeReplacement, isBabelParseError, isLoca
|
|||||||
*/
|
*/
|
||||||
export function makeEs5TranslatePlugin(
|
export function makeEs5TranslatePlugin(
|
||||||
diagnostics: Diagnostics, translations: Record<string, ɵParsedTranslation>,
|
diagnostics: Diagnostics, translations: Record<string, ɵParsedTranslation>,
|
||||||
{missingTranslation = 'error', localizeName = '$localize'}: TranslatePluginOptions = {}):
|
{missingTranslation = 'error', localizeName = '$localize'}: TranslatePluginOptions = {},
|
||||||
PluginObj {
|
fs: FileSystem = getFileSystem()): PluginObj {
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
CallExpression(callPath: NodePath<CallExpression>) {
|
CallExpression(callPath: NodePath<CallExpression>) {
|
||||||
try {
|
try {
|
||||||
const calleePath = callPath.get('callee');
|
const calleePath = callPath.get('callee');
|
||||||
if (isLocalize(calleePath, localizeName)) {
|
if (isLocalize(calleePath, localizeName)) {
|
||||||
const [messageParts] = unwrapMessagePartsFromLocalizeCall(callPath);
|
const [messageParts] = unwrapMessagePartsFromLocalizeCall(callPath, fs);
|
||||||
const [expressions] = unwrapSubstitutionsFromLocalizeCall(callPath);
|
const [expressions] = unwrapSubstitutionsFromLocalizeCall(callPath, fs);
|
||||||
const translated =
|
const translated =
|
||||||
translate(diagnostics, translations, messageParts, expressions, missingTranslation);
|
translate(diagnostics, translations, messageParts, expressions, missingTranslation);
|
||||||
callPath.replaceWith(buildLocalizeReplacement(translated[0], translated[1]));
|
callPath.replaceWith(buildLocalizeReplacement(translated[0], translated[1]));
|
||||||
|
@ -78,8 +78,8 @@ export class SourceFileTranslationHandler implements TranslationHandler {
|
|||||||
generatorOpts: {minified: true},
|
generatorOpts: {minified: true},
|
||||||
plugins: [
|
plugins: [
|
||||||
makeLocalePlugin(translationBundle.locale),
|
makeLocalePlugin(translationBundle.locale),
|
||||||
makeEs2015TranslatePlugin(diagnostics, translationBundle.translations, options),
|
makeEs2015TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs),
|
||||||
makeEs5TranslatePlugin(diagnostics, translationBundle.translations, options),
|
makeEs5TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs),
|
||||||
],
|
],
|
||||||
filename,
|
filename,
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, setFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
|
import {InvalidFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/src/invalid_file_system';
|
||||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||||
import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing';
|
import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing';
|
||||||
import {loadTestDirectory} from '@angular/compiler-cli/test/helpers';
|
import {loadTestDirectory} from '@angular/compiler-cli/test/helpers';
|
||||||
@ -34,6 +35,7 @@ runInEachFileSystem(() => {
|
|||||||
|
|
||||||
fs.ensureDir(fs.dirname(sourceFilePath));
|
fs.ensureDir(fs.dirname(sourceFilePath));
|
||||||
loadTestDirectory(fs, __dirname + '/test_files', absoluteFrom('/project/test_files'));
|
loadTestDirectory(fs, __dirname + '/test_files', absoluteFrom('/project/test_files'));
|
||||||
|
setFileSystem(new InvalidFileSystem());
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extractTranslations()', () => {
|
describe('extractTranslations()', () => {
|
||||||
@ -48,6 +50,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds: false,
|
useLegacyIds: false,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`{`,
|
`{`,
|
||||||
@ -70,6 +73,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds,
|
useLegacyIds,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`{`,
|
`{`,
|
||||||
@ -97,6 +101,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds,
|
useLegacyIds,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
||||||
@ -151,6 +156,7 @@ runInEachFileSystem(() => {
|
|||||||
useLegacyIds,
|
useLegacyIds,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
formatOptions,
|
formatOptions,
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
||||||
@ -222,6 +228,7 @@ runInEachFileSystem(() => {
|
|||||||
useLegacyIds,
|
useLegacyIds,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
formatOptions,
|
formatOptions,
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
||||||
@ -299,6 +306,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: true,
|
useSourceMaps: true,
|
||||||
useLegacyIds: false,
|
useLegacyIds: false,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
`<?xml version="1.0" encoding="UTF-8" ?>`,
|
||||||
@ -308,7 +316,8 @@ runInEachFileSystem(() => {
|
|||||||
` <trans-unit id="157258427077572998" datatype="html">`,
|
` <trans-unit id="157258427077572998" datatype="html">`,
|
||||||
` <source>Message in <x id="a-file" equiv-text="file"/>!</source>`,
|
` <source>Message in <x id="a-file" equiv-text="file"/>!</source>`,
|
||||||
` <context-group purpose="location">`,
|
` <context-group purpose="location">`,
|
||||||
// These source file paths are due to how Bazel TypeScript compilation source-maps work
|
// These source file paths are due to how Bazel TypeScript compilation source-maps
|
||||||
|
// work
|
||||||
` <context context-type="sourcefile">../packages/localize/src/tools/test/extract/integration/test_files/src/a.ts</context>`,
|
` <context context-type="sourcefile">../packages/localize/src/tools/test/extract/integration/test_files/src/a.ts</context>`,
|
||||||
` <context context-type="linenumber">3</context>`,
|
` <context context-type="linenumber">3</context>`,
|
||||||
` </context-group>`,
|
` </context-group>`,
|
||||||
@ -339,6 +348,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds: false,
|
useLegacyIds: false,
|
||||||
duplicateMessageHandling: 'error',
|
duplicateMessageHandling: 'error',
|
||||||
|
fileSystem: fs,
|
||||||
}))
|
}))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Failed to extract messages\n` +
|
`Failed to extract messages\n` +
|
||||||
@ -360,6 +370,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds: false,
|
useLegacyIds: false,
|
||||||
duplicateMessageHandling: 'warning',
|
duplicateMessageHandling: 'warning',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(logger.logs.warn).toEqual([
|
expect(logger.logs.warn).toEqual([
|
||||||
['Messages extracted with warnings\n' +
|
['Messages extracted with warnings\n' +
|
||||||
@ -390,6 +401,7 @@ runInEachFileSystem(() => {
|
|||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
useLegacyIds: false,
|
useLegacyIds: false,
|
||||||
duplicateMessageHandling: 'ignore',
|
duplicateMessageHandling: 'ignore',
|
||||||
|
fileSystem: fs,
|
||||||
});
|
});
|
||||||
expect(logger.logs.warn).toEqual([]);
|
expect(logger.logs.warn).toEqual([]);
|
||||||
expect(fs.readFile(outputPath)).toEqual([
|
expect(fs.readFile(outputPath)).toEqual([
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {absoluteFrom, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||||
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
|
||||||
|
|
||||||
@ -14,6 +14,8 @@ import {XmbTranslationSerializer} from '../../../src/extract/translation_files/x
|
|||||||
import {mockMessage} from './mock_message';
|
import {mockMessage} from './mock_message';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
|
let fs: FileSystem;
|
||||||
|
beforeEach(() => fs = getFileSystem());
|
||||||
describe('XmbTranslationSerializer', () => {
|
describe('XmbTranslationSerializer', () => {
|
||||||
[false, true].forEach(useLegacyIds => {
|
[false, true].forEach(useLegacyIds => {
|
||||||
describe(`renderFile() [using ${useLegacyIds ? 'legacy' : 'canonical'} ids]`, () => {
|
describe(`renderFile() [using ${useLegacyIds ? 'legacy' : 'canonical'} ids]`, () => {
|
||||||
@ -61,7 +63,8 @@ runInEachFileSystem(() => {
|
|||||||
],
|
],
|
||||||
[], {}),
|
[], {}),
|
||||||
];
|
];
|
||||||
const serializer = new XmbTranslationSerializer(absoluteFrom('/project'), useLegacyIds);
|
const serializer =
|
||||||
|
new XmbTranslationSerializer(absoluteFrom('/project'), useLegacyIds, fs);
|
||||||
const output = serializer.serialize(messages);
|
const output = serializer.serialize(messages);
|
||||||
expect(output).toContain([
|
expect(output).toContain([
|
||||||
`<messagebundle>`,
|
`<messagebundle>`,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {absoluteFrom, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||||
import {ɵmakeTemplateObject} from '@angular/localize';
|
import {ɵmakeTemplateObject} from '@angular/localize';
|
||||||
import {NodePath, TransformOptions, transformSync} from '@babel/core';
|
import {NodePath, TransformOptions, transformSync} from '@babel/core';
|
||||||
@ -16,6 +16,8 @@ import {Expression, Identifier, TaggedTemplateExpression, ExpressionStatement, C
|
|||||||
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils';
|
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
|
let fs: FileSystem;
|
||||||
|
beforeEach(() => fs = getFileSystem());
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
describe('isNamedIdentifier()', () => {
|
describe('isNamedIdentifier()', () => {
|
||||||
it('should return true if the expression is an identifier with name `$localize`', () => {
|
it('should return true if the expression is an identifier with name `$localize`', () => {
|
||||||
@ -77,7 +79,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should return an array of string literals and locations from a direct call to a tag function',
|
it('should return an array of string literals and locations from a direct call to a tag function',
|
||||||
() => {
|
() => {
|
||||||
const localizeCall = getLocalizeCall(`$localize(['a', 'b\\t', 'c'], 1, 2)`);
|
const localizeCall = getLocalizeCall(`$localize(['a', 'b\\t', 'c'], 1, 2)`);
|
||||||
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall);
|
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
|
||||||
expect(parts).toEqual(['a', 'b\t', 'c']);
|
expect(parts).toEqual(['a', 'b\t', 'c']);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
{
|
{
|
||||||
@ -105,7 +107,7 @@ runInEachFileSystem(() => {
|
|||||||
() => {
|
() => {
|
||||||
let localizeCall = getLocalizeCall(
|
let localizeCall = getLocalizeCall(
|
||||||
`$localize(__makeTemplateObject(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c']), 1, 2)`);
|
`$localize(__makeTemplateObject(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c']), 1, 2)`);
|
||||||
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall);
|
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
|
||||||
expect(parts).toEqual(['a', 'b\t', 'c']);
|
expect(parts).toEqual(['a', 'b\t', 'c']);
|
||||||
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
@ -138,7 +140,7 @@ runInEachFileSystem(() => {
|
|||||||
return _templateObject = function() { return e }, e
|
return _templateObject = function() { return e }, e
|
||||||
}
|
}
|
||||||
$localize(_templateObject(), 1, 2)`);
|
$localize(_templateObject(), 1, 2)`);
|
||||||
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall);
|
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
|
||||||
expect(parts).toEqual(['a', 'b\t', 'c']);
|
expect(parts).toEqual(['a', 'b\t', 'c']);
|
||||||
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
@ -173,7 +175,7 @@ runInEachFileSystem(() => {
|
|||||||
const localizeStatement = localizeCall.parentPath as NodePath<ExpressionStatement>;
|
const localizeStatement = localizeCall.parentPath as NodePath<ExpressionStatement>;
|
||||||
const statements = localizeStatement.container as object[];
|
const statements = localizeStatement.container as object[];
|
||||||
expect(statements.length).toEqual(2);
|
expect(statements.length).toEqual(2);
|
||||||
unwrapMessagePartsFromLocalizeCall(localizeCall);
|
unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
|
||||||
expect(statements.length).toEqual(1);
|
expect(statements.length).toEqual(1);
|
||||||
expect(statements[0]).toBe(localizeStatement.node);
|
expect(statements[0]).toBe(localizeStatement.node);
|
||||||
});
|
});
|
||||||
@ -183,7 +185,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should return the substitutions and locations from a direct call to a tag function',
|
it('should return the substitutions and locations from a direct call to a tag function',
|
||||||
() => {
|
() => {
|
||||||
const call = getLocalizeCall(`$localize(['a', 'b\t', 'c'], 1, 2)`);
|
const call = getLocalizeCall(`$localize(['a', 'b\t', 'c'], 1, 2)`);
|
||||||
const [substitutions, locations] = unwrapSubstitutionsFromLocalizeCall(call);
|
const [substitutions, locations] = unwrapSubstitutionsFromLocalizeCall(call, fs);
|
||||||
expect((substitutions as NumericLiteral[]).map(s => s.value)).toEqual([1, 2]);
|
expect((substitutions as NumericLiteral[]).map(s => s.value)).toEqual([1, 2]);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
{
|
{
|
||||||
@ -204,7 +206,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should return the substitutions and locations from a downleveled tagged template', () => {
|
it('should return the substitutions and locations from a downleveled tagged template', () => {
|
||||||
const call = getLocalizeCall(
|
const call = getLocalizeCall(
|
||||||
`$localize(__makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), 1, 2)`);
|
`$localize(__makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), 1, 2)`);
|
||||||
const [substitutions, locations] = unwrapSubstitutionsFromLocalizeCall(call);
|
const [substitutions, locations] = unwrapSubstitutionsFromLocalizeCall(call, fs);
|
||||||
expect((substitutions as NumericLiteral[]).map(s => s.value)).toEqual([1, 2]);
|
expect((substitutions as NumericLiteral[]).map(s => s.value)).toEqual([1, 2]);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
{
|
{
|
||||||
@ -226,7 +228,8 @@ runInEachFileSystem(() => {
|
|||||||
describe('unwrapMessagePartsFromTemplateLiteral', () => {
|
describe('unwrapMessagePartsFromTemplateLiteral', () => {
|
||||||
it('should return a TemplateStringsArray built from the template literal elements', () => {
|
it('should return a TemplateStringsArray built from the template literal elements', () => {
|
||||||
const taggedTemplate = getTaggedTemplate('$localize `a${1}b\\t${2}c`;');
|
const taggedTemplate = getTaggedTemplate('$localize `a${1}b\\t${2}c`;');
|
||||||
expect(unwrapMessagePartsFromTemplateLiteral(taggedTemplate.get('quasi').get('quasis'))[0])
|
expect(
|
||||||
|
unwrapMessagePartsFromTemplateLiteral(taggedTemplate.get('quasi').get('quasis'), fs)[0])
|
||||||
.toEqual(ɵmakeTemplateObject(['a', 'b\t', 'c'], ['a', 'b\\t', 'c']));
|
.toEqual(ɵmakeTemplateObject(['a', 'b\t', 'c'], ['a', 'b\\t', 'c']));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -248,7 +251,7 @@ runInEachFileSystem(() => {
|
|||||||
describe('unwrapStringLiteralArray', () => {
|
describe('unwrapStringLiteralArray', () => {
|
||||||
it('should return an array of string from an array expression', () => {
|
it('should return an array of string from an array expression', () => {
|
||||||
const array = getFirstExpression(`['a', 'b', 'c']`);
|
const array = getFirstExpression(`['a', 'b', 'c']`);
|
||||||
const [expressions, locations] = unwrapStringLiteralArray(array);
|
const [expressions, locations] = unwrapStringLiteralArray(array, fs);
|
||||||
expect(expressions).toEqual(['a', 'b', 'c']);
|
expect(expressions).toEqual(['a', 'b', 'c']);
|
||||||
expect(locations).toEqual([
|
expect(locations).toEqual([
|
||||||
{
|
{
|
||||||
@ -274,7 +277,7 @@ runInEachFileSystem(() => {
|
|||||||
|
|
||||||
it('should throw an error if any elements of the array are not literal strings', () => {
|
it('should throw an error if any elements of the array are not literal strings', () => {
|
||||||
const array = getFirstExpression(`['a', 2, 'c']`);
|
const array = getFirstExpression(`['a', 2, 'c']`);
|
||||||
expect(() => unwrapStringLiteralArray(array))
|
expect(() => unwrapStringLiteralArray(array, fs))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
'Unexpected messageParts for `$localize` (expected an array of strings).');
|
'Unexpected messageParts for `$localize` (expected an array of strings).');
|
||||||
});
|
});
|
||||||
@ -315,7 +318,7 @@ runInEachFileSystem(() => {
|
|||||||
filename: 'src/test.js',
|
filename: 'src/test.js',
|
||||||
sourceRoot: '/root',
|
sourceRoot: '/root',
|
||||||
});
|
});
|
||||||
const location = getLocation(taggedTemplate)!;
|
const location = getLocation(fs, taggedTemplate)!;
|
||||||
expect(location).toBeDefined();
|
expect(location).toBeDefined();
|
||||||
expect(location.start).toEqual({line: 0, column: 10});
|
expect(location.start).toEqual({line: 0, column: 10});
|
||||||
expect(location.start.constructor.name).toEqual('Object');
|
expect(location.start.constructor.name).toEqual('Object');
|
||||||
@ -327,7 +330,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should return `undefined` if the NodePath has no filename', () => {
|
it('should return `undefined` if the NodePath has no filename', () => {
|
||||||
const taggedTemplate = getTaggedTemplate(
|
const taggedTemplate = getTaggedTemplate(
|
||||||
'const x = $localize ``;', {sourceRoot: '/root', filename: undefined});
|
'const x = $localize ``;', {sourceRoot: '/root', filename: undefined});
|
||||||
const location = getLocation(taggedTemplate);
|
const location = getLocation(fs, taggedTemplate);
|
||||||
expect(location).toBeUndefined();
|
expect(location).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||||
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
|
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
|
||||||
import {ɵParsedTranslation} from '@angular/localize/private';
|
import {ɵParsedTranslation} from '@angular/localize/private';
|
||||||
@ -15,6 +16,8 @@ import {TranslatePluginOptions} from '../../../src/source_file_utils';
|
|||||||
import {makeEs2015TranslatePlugin} from '../../../src/translate/source_files/es2015_translate_plugin';
|
import {makeEs2015TranslatePlugin} from '../../../src/translate/source_files/es2015_translate_plugin';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
|
let fs: FileSystem;
|
||||||
|
beforeEach(() => fs = getFileSystem());
|
||||||
describe('makeEs2015Plugin', () => {
|
describe('makeEs2015Plugin', () => {
|
||||||
describe('(no translations)', () => {
|
describe('(no translations)', () => {
|
||||||
it('should transform `$localize` tags with binary expression', () => {
|
it('should transform `$localize` tags with binary expression', () => {
|
||||||
@ -168,13 +171,13 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function transformCode(
|
function transformCode(
|
||||||
input: string, translations: Record<string, ɵParsedTranslation> = {},
|
input: string, translations: Record<string, ɵParsedTranslation> = {},
|
||||||
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
|
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
|
||||||
return transformSync(input, {
|
return transformSync(input, {
|
||||||
plugins: [makeEs2015TranslatePlugin(diagnostics, translations, pluginOptions)],
|
plugins: [makeEs2015TranslatePlugin(diagnostics, translations, pluginOptions)],
|
||||||
filename: '/app/dist/test.js'
|
filename: '/app/dist/test.js'
|
||||||
})!.code!;
|
})!.code!;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||||
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
|
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
|
||||||
import {ɵParsedTranslation} from '@angular/localize/private';
|
import {ɵParsedTranslation} from '@angular/localize/private';
|
||||||
@ -15,6 +16,8 @@ import {TranslatePluginOptions} from '../../../src/source_file_utils';
|
|||||||
import {makeEs5TranslatePlugin} from '../../../src/translate/source_files/es5_translate_plugin';
|
import {makeEs5TranslatePlugin} from '../../../src/translate/source_files/es5_translate_plugin';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
|
let fs: FileSystem;
|
||||||
|
beforeEach(() => fs = getFileSystem());
|
||||||
describe('makeEs5Plugin', () => {
|
describe('makeEs5Plugin', () => {
|
||||||
describe('(no translations)', () => {
|
describe('(no translations)', () => {
|
||||||
it('should transform `$localize` calls with binary expression', () => {
|
it('should transform `$localize` calls with binary expression', () => {
|
||||||
@ -357,13 +360,13 @@ runInEachFileSystem(() => {
|
|||||||
expect(output).toEqual('"abc" + (1 + 2 + 3) + " - Hello, " + getName() + "!";');
|
expect(output).toEqual('"abc" + (1 + 2 + 3) + " - Hello, " + getName() + "!";');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function transformCode(
|
function transformCode(
|
||||||
input: string, translations: Record<string, ɵParsedTranslation> = {},
|
input: string, translations: Record<string, ɵParsedTranslation> = {},
|
||||||
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
|
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
|
||||||
return transformSync(input, {
|
return transformSync(input, {
|
||||||
plugins: [makeEs5TranslatePlugin(diagnostics, translations, pluginOptions)],
|
plugins: [makeEs5TranslatePlugin(diagnostics, translations, pluginOptions)],
|
||||||
filename: '/app/dist/test.js'
|
filename: '/app/dist/test.js'
|
||||||
})!.code!;
|
})!.code!;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user