Skip to content

Commit

Permalink
refactor(localize): avoid free-standing FileSystem functions (#39006)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
petebacondarwin authored and josephperrott committed Sep 30, 2020
1 parent ac3aa04 commit 79620f5
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 87 deletions.
4 changes: 2 additions & 2 deletions packages/localize/src/tools/src/extract/extraction.ts
Expand Up @@ -52,8 +52,8 @@ export class MessageExtractor {
sourceRoot: this.basePath,
filename,
plugins: [
makeEs2015ExtractPlugin(messages, this.localizeName),
makeEs5ExtractPlugin(messages, this.localizeName),
makeEs2015ExtractPlugin(this.fs, messages, this.localizeName),
makeEs5ExtractPlugin(this.fs, messages, this.localizeName),
],
code: false,
ast: false
Expand Down
27 changes: 17 additions & 10 deletions packages/localize/src/tools/src/extract/main.ts
Expand Up @@ -6,13 +6,13 @@
* 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 {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 {ɵParsedMessage} from '@angular/localize';
import * as glob from 'glob';
import * as yargs from 'yargs';

import {DiagnosticHandlingStrategy, Diagnostics} from '../diagnostics';
import {DiagnosticHandlingStrategy} from '../diagnostics';

import {checkDuplicateMessages} from './duplicates';
import {MessageExtractor} from './extraction';
Expand Down Expand Up @@ -97,8 +97,8 @@ if (require.main === module) {
.help()
.parse(args);

const fs = new NodeJSFileSystem();
setFileSystem(fs);
const fileSystem = new NodeJSFileSystem();
setFileSystem(fileSystem);

const rootPath = options.r;
const sourceFilePaths = glob.sync(options.s, {cwd: rootPath, nodir: true});
Expand All @@ -119,6 +119,7 @@ if (require.main === module) {
useLegacyIds: options.useLegacyIds,
duplicateMessageHandling,
formatOptions,
fileSystem,
});
}

Expand Down Expand Up @@ -166,6 +167,10 @@ export interface ExtractTranslationsOptions {
* A collection of formatting options to pass to the translation file serializer.
*/
formatOptions?: FormatOptions;
/**
* The file-system abstraction to use.
*/
fileSystem: FileSystem;
}

export function extractTranslations({
Expand All @@ -179,8 +184,8 @@ export function extractTranslations({
useLegacyIds,
duplicateMessageHandling,
formatOptions = {},
fileSystem: fs,
}: ExtractTranslationsOptions) {
const fs = getFileSystem();
const basePath = fs.resolve(rootPath);
const extractor = new MessageExtractor(fs, logger, {basePath, useSourceMaps});

Expand All @@ -196,7 +201,7 @@ export function extractTranslations({

const outputPath = fs.resolve(rootPath, output);
const serializer =
getSerializer(format, sourceLocale, fs.dirname(outputPath), useLegacyIds, formatOptions);
getSerializer(format, sourceLocale, fs.dirname(outputPath), useLegacyIds, formatOptions, fs);
const translationFile = serializer.serialize(messages);
fs.ensureDir(fs.dirname(outputPath));
fs.writeFile(outputPath, translationFile);
Expand All @@ -208,18 +213,20 @@ export function extractTranslations({

export function getSerializer(
format: string, sourceLocale: string, rootPath: AbsoluteFsPath, useLegacyIds: boolean,
formatOptions: FormatOptions = {}): TranslationSerializer {
formatOptions: FormatOptions = {}, fs: FileSystem): TranslationSerializer {
switch (format) {
case 'xlf':
case 'xlif':
case 'xliff':
return new Xliff1TranslationSerializer(sourceLocale, rootPath, useLegacyIds, formatOptions);
return new Xliff1TranslationSerializer(
sourceLocale, rootPath, useLegacyIds, formatOptions, fs);
case 'xlf2':
case 'xlif2':
case 'xliff2':
return new Xliff2TranslationSerializer(sourceLocale, rootPath, useLegacyIds, formatOptions);
return new Xliff2TranslationSerializer(
sourceLocale, rootPath, useLegacyIds, formatOptions, fs);
case 'xmb':
return new XmbTranslationSerializer(rootPath, useLegacyIds);
return new XmbTranslationSerializer(rootPath, useLegacyIds, fs);
case 'json':
return new SimpleJsonTranslationSerializer(sourceLocale);
}
Expand Down
Expand Up @@ -5,25 +5,26 @@
* 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 {FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
import {NodePath, PluginObj} from '@babel/core';
import {TaggedTemplateExpression} from '@babel/types';

import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapExpressionsFromTemplateLiteral, unwrapMessagePartsFromTemplateLiteral} from '../../source_file_utils';

export function makeEs2015ExtractPlugin(
messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
fs: FileSystem, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
return {
visitor: {
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
const tag = path.get('tag');
if (isNamedIdentifier(tag, localizeName) && isGlobalIdentifier(tag)) {
const quasiPath = path.get('quasi');
const [messageParts, messagePartLocations] =
unwrapMessagePartsFromTemplateLiteral(quasiPath.get('quasis'));
unwrapMessagePartsFromTemplateLiteral(quasiPath.get('quasis'), fs);
const [expressions, expressionLocations] =
unwrapExpressionsFromTemplateLiteral(quasiPath);
const location = getLocation(quasiPath);
unwrapExpressionsFromTemplateLiteral(quasiPath, fs);
const location = getLocation(fs, quasiPath);
const message = ɵparseMessage(
messageParts, expressions, location, messagePartLocations, expressionLocations);
messages.push(message);
Expand Down
Expand Up @@ -5,23 +5,26 @@
* 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 {FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵParsedMessage, ɵparseMessage} from '@angular/localize';
import {NodePath, PluginObj} from '@babel/core';
import {CallExpression} from '@babel/types';

import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapMessagePartsFromLocalizeCall, unwrapSubstitutionsFromLocalizeCall} from '../../source_file_utils';

export function makeEs5ExtractPlugin(
messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
fs: FileSystem, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
return {
visitor: {
CallExpression(callPath: NodePath<CallExpression>) {
const calleePath = callPath.get('callee');
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
const [messageParts, messagePartLocations] = unwrapMessagePartsFromLocalizeCall(callPath);
const [expressions, expressionLocations] = unwrapSubstitutionsFromLocalizeCall(callPath);
const [messageParts, messagePartLocations] =
unwrapMessagePartsFromLocalizeCall(callPath, fs);
const [expressions, expressionLocations] =
unwrapSubstitutionsFromLocalizeCall(callPath, fs);
const [messagePartsArg, expressionsArg] = callPath.get('arguments');
const location = getLocation(messagePartsArg, expressionsArg);
const location = getLocation(fs, messagePartsArg, expressionsArg);
const message = ɵparseMessage(
messageParts, expressions, location, messagePartLocations, expressionLocations);
messages.push(message);
Expand Down
Expand Up @@ -5,7 +5,7 @@
* 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, 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 {FormatOptions, validateOptions} from './format_options';
Expand All @@ -28,7 +28,7 @@ const LEGACY_XLIFF_MESSAGE_LENGTH = 40;
export class Xliff1TranslationSerializer implements TranslationSerializer {
constructor(
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);
}

Expand Down Expand Up @@ -115,7 +115,7 @@ export class Xliff1TranslationSerializer implements TranslationSerializer {

private serializeLocation(xml: XmlFile, location: ɵSourceLocation): void {
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 ?
`,${location.end.line + 1}` :
'';
Expand Down
Expand Up @@ -5,7 +5,7 @@
* 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, 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 {FormatOptions, validateOptions} from './format_options';
Expand All @@ -28,7 +28,7 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
private currentPlaceholderId = 0;
constructor(
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);
}

Expand Down Expand Up @@ -64,7 +64,7 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
end !== undefined && end.line !== start.line ? `,${end.line + 1}` : '';
this.serializeNote(
xml, 'location',
`${relative(this.basePath, file)}:${start.line + 1}${endLineString}`);
`${this.fs.relative(this.basePath, file)}:${start.line + 1}${endLineString}`);
}
if (message.description) {
this.serializeNote(xml, 'description', message.description);
Expand Down
Expand Up @@ -5,7 +5,7 @@
* 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, 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 {extractIcuPlaceholders} from './icu_parsing';
Expand All @@ -21,7 +21,9 @@ import {XmlFile} from './xml_file';
* @publicApi used by CLI
*/
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 {
const ids = new Set<string>();
Expand Down Expand Up @@ -74,7 +76,8 @@ export class XmbTranslationSerializer implements TranslationSerializer {
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
`,${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');
}

Expand Down

0 comments on commit 79620f5

Please sign in to comment.