diff --git a/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts b/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts index 27ae97333a..41b8f53fc1 100644 --- a/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts +++ b/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts @@ -35,7 +35,7 @@ export function makeEs5ExtractPlugin( // If we get a BabelParseError here then something went wrong with Babel itself // since there must be something wrong with the structure of the AST generated // by Babel parsing a TaggedTemplateExpression. - throw buildCodeFrameError(callPath, e); + throw buildCodeFrameError(fs, callPath, e); } else { throw e; } diff --git a/packages/localize/src/tools/src/source_file_utils.ts b/packages/localize/src/tools/src/source_file_utils.ts index 55cd10c5fa..4d22565987 100644 --- a/packages/localize/src/tools/src/source_file_utils.ts +++ b/packages/localize/src/tools/src/source_file_utils.ts @@ -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, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system'; +import {absoluteFrom, AbsoluteFsPath, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system'; import {ɵisMissingTranslationError, ɵmakeTemplateObject, ɵParsedTranslation, ɵSourceLocation, ɵtranslate} from '@angular/localize'; import {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; @@ -400,8 +400,19 @@ export function isBabelParseError(e: any): e is BabelParseError { return e.type === 'BabelParseError'; } -export function buildCodeFrameError(path: NodePath, e: BabelParseError): string { - const filename = path.hub.file.opts.filename || '(unknown file)'; +export function buildCodeFrameError( + fs: PathManipulation, path: NodePath, e: BabelParseError): string { + let filename = path.hub.file.opts.filename; + if (filename) { + filename = fs.resolve(filename); + let cwd = path.hub.file.opts.cwd; + if (cwd) { + cwd = fs.resolve(cwd); + filename = fs.relative(cwd, filename); + } + } else { + filename = '(unknown file)'; + } const message = path.hub.file.buildCodeFrameError(e.node, e.message).message; return `${filename}: ${message}`; } @@ -434,9 +445,14 @@ export function serializeLocationPosition(location: ɵSourceLocation): string { function getFileFromPath(fs: PathManipulation, path: NodePath|undefined): AbsoluteFsPath|null { const opts = path?.hub.file.opts; - return opts?.filename ? - fs.resolve(opts.generatorOpts.sourceRoot ?? opts.cwd, fs.relative(opts.cwd, opts.filename)) : - null; + const filename = opts?.filename; + if (!filename) { + return null; + } + const relativePath = fs.relative(opts.cwd, filename); + const root = opts.generatorOpts.sourceRoot ?? opts.cwd; + const absPath = fs.resolve(root, relativePath); + return absPath; } function getLineAndColumn(loc: {line: number, column: number}): {line: number, column: number} { diff --git a/packages/localize/src/tools/src/translate/main.ts b/packages/localize/src/tools/src/translate/main.ts index b993a0c3db..f0ef79e12b 100644 --- a/packages/localize/src/tools/src/translate/main.ts +++ b/packages/localize/src/tools/src/translate/main.ts @@ -105,7 +105,7 @@ if (require.main === module) { const sourceRootPath = options.r; const sourceFilePaths = glob.sync(options.s, {cwd: sourceRootPath, nodir: true}); const translationFilePaths: (string|string[])[] = convertArraysFromArgs(options.t); - const outputPathFn = getOutputPathFn(fs.resolve(options.o)); + const outputPathFn = getOutputPathFn(fs, fs.resolve(options.o)); const diagnostics = new Diagnostics(); const missingTranslation = options.m as DiagnosticHandlingStrategy; const duplicateTranslation = options.d as DiagnosticHandlingStrategy; diff --git a/packages/localize/src/tools/src/translate/output_path.ts b/packages/localize/src/tools/src/translate/output_path.ts index cfa3c8e609..2285794bdb 100644 --- a/packages/localize/src/tools/src/translate/output_path.ts +++ b/packages/localize/src/tools/src/translate/output_path.ts @@ -5,8 +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} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {join} from 'path'; +import {AbsoluteFsPath, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system'; /** * A function that will return an absolute path to where a file is to be written, given a locale and @@ -23,8 +22,8 @@ export interface OutputPathFn { * The special `{{LOCALE}}` marker will be replaced with the locale code of the current translation. * @param outputFolder An absolute path to the folder containing this set of translations. */ -export function getOutputPathFn(outputFolder: AbsoluteFsPath): OutputPathFn { +export function getOutputPathFn(fs: PathManipulation, outputFolder: AbsoluteFsPath): OutputPathFn { const [pre, post] = outputFolder.split('{{LOCALE}}'); - return post === undefined ? (_locale, relativePath) => join(pre, relativePath) : - (locale, relativePath) => join(pre + locale + post, relativePath); + return post === undefined ? (_locale, relativePath) => fs.join(pre, relativePath) : + (locale, relativePath) => fs.join(pre + locale + post, relativePath); } diff --git a/packages/localize/src/tools/src/translate/source_files/es2015_translate_plugin.ts b/packages/localize/src/tools/src/translate/source_files/es2015_translate_plugin.ts index be1f351361..a3aa49ea60 100644 --- a/packages/localize/src/tools/src/translate/source_files/es2015_translate_plugin.ts +++ b/packages/localize/src/tools/src/translate/source_files/es2015_translate_plugin.ts @@ -41,7 +41,7 @@ export function makeEs2015TranslatePlugin( // If we get a BabelParseError here then something went wrong with Babel itself // since there must be something wrong with the structure of the AST generated // by Babel parsing a TaggedTemplateExpression. - throw buildCodeFrameError(path, e); + throw buildCodeFrameError(fs, path, e); } else { throw e; } diff --git a/packages/localize/src/tools/src/translate/source_files/es5_translate_plugin.ts b/packages/localize/src/tools/src/translate/source_files/es5_translate_plugin.ts index 6a98ff8729..b8f1bf4c65 100644 --- a/packages/localize/src/tools/src/translate/source_files/es5_translate_plugin.ts +++ b/packages/localize/src/tools/src/translate/source_files/es5_translate_plugin.ts @@ -37,7 +37,7 @@ export function makeEs5TranslatePlugin( } } catch (e) { if (isBabelParseError(e)) { - diagnostics.error(buildCodeFrameError(callPath, e)); + diagnostics.error(buildCodeFrameError(fs, callPath, e)); } else { throw e; } diff --git a/packages/localize/src/tools/src/translate/source_files/source_file_translation_handler.ts b/packages/localize/src/tools/src/translate/source_files/source_file_translation_handler.ts index c3d69b4206..8c71aed038 100644 --- a/packages/localize/src/tools/src/translate/source_files/source_file_translation_handler.ts +++ b/packages/localize/src/tools/src/translate/source_files/source_file_translation_handler.ts @@ -81,6 +81,7 @@ export class SourceFileTranslationHandler implements TranslationHandler { makeEs2015TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs), makeEs5TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs), ], + cwd: sourceRoot, filename, }); if (translated && translated.code) { diff --git a/packages/localize/src/tools/test/BUILD.bazel b/packages/localize/src/tools/test/BUILD.bazel index 705826ea14..1e2d6d768c 100644 --- a/packages/localize/src/tools/test/BUILD.bazel +++ b/packages/localize/src/tools/test/BUILD.bazel @@ -15,6 +15,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/logging/testing", "//packages/localize", "//packages/localize/src/tools", + "//packages/localize/src/tools/test/helpers", "//packages/localize/src/utils", "@npm//@babel/core", "@npm//@babel/generator", diff --git a/packages/localize/src/tools/test/extract/extractor_spec.ts b/packages/localize/src/tools/test/extract/extractor_spec.ts index 48d380915c..d2dfa90305 100644 --- a/packages/localize/src/tools/test/extract/extractor_spec.ts +++ b/packages/localize/src/tools/test/extract/extractor_spec.ts @@ -5,13 +5,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 {absoluteFrom, getFileSystem, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {absoluteFrom, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing'; import {MessageExtractor} from '../../src/extract/extraction'; +import {runInNativeFileSystem} from '../helpers'; -runInEachFileSystem(() => { +runInNativeFileSystem(() => { describe('extractMessages', () => { it('should extract a message for each $localize template tag', () => { const fs = getFileSystem(); @@ -20,7 +20,7 @@ runInEachFileSystem(() => { const filename = 'relative/path.js'; const file = fs.resolve(basePath, filename); const extractor = new MessageExtractor(fs, logger, {basePath}); - fs.ensureDir(absoluteFrom('/root/path/relative')); + fs.ensureDir(fs.dirname(file)); fs.writeFile(file, [ '$localize`:meaning|description:a${1}b${2}c`;', '$localize(__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"]), 1, 2);', @@ -40,19 +40,19 @@ runInEachFileSystem(() => { { start: {line: 0, column: 10}, end: {line: 0, column: 32}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: ':meaning|description:a', }, { start: {line: 0, column: 36}, end: {line: 0, column: 37}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: 'b', }, { start: {line: 0, column: 41}, end: {line: 0, column: 42}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: 'c', } ], @@ -60,18 +60,8 @@ runInEachFileSystem(() => { placeholderNames: ['PH', 'PH_1'], substitutions: jasmine.any(Object), substitutionLocations: { - PH: { - start: {line: 0, column: 34}, - end: {line: 0, column: 35}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '1' - }, - PH_1: { - start: {line: 0, column: 39}, - end: {line: 0, column: 40}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '2' - } + PH: {start: {line: 0, column: 34}, end: {line: 0, column: 35}, file, text: '1'}, + PH_1: {start: {line: 0, column: 39}, end: {line: 0, column: 40}, file, text: '2'} }, legacyIds: [], location: { @@ -92,19 +82,19 @@ runInEachFileSystem(() => { { start: {line: 1, column: 69}, end: {line: 1, column: 72}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: '"a"', }, { start: {line: 1, column: 74}, end: {line: 1, column: 97}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: '":custom-placeholder:b"', }, { start: {line: 1, column: 99}, end: {line: 1, column: 102}, - file: absoluteFrom('/root/path/relative/path.js'), + file, text: '"c"', } ], @@ -112,18 +102,9 @@ runInEachFileSystem(() => { placeholderNames: ['custom-placeholder', 'PH_1'], substitutions: jasmine.any(Object), substitutionLocations: { - 'custom-placeholder': { - start: {line: 1, column: 106}, - end: {line: 1, column: 107}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '1' - }, - PH_1: { - start: {line: 1, column: 109}, - end: {line: 1, column: 110}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '2' - } + 'custom-placeholder': + {start: {line: 1, column: 106}, end: {line: 1, column: 107}, file, text: '1'}, + PH_1: {start: {line: 1, column: 109}, end: {line: 1, column: 110}, file, text: '2'} }, legacyIds: [], location: { @@ -145,38 +126,13 @@ runInEachFileSystem(() => { placeholderNames: ['PH', 'PH_1'], substitutions: jasmine.any(Object), substitutionLocations: { - PH: { - start: {line: 2, column: 26}, - end: {line: 2, column: 27}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '1' - }, - PH_1: { - start: {line: 2, column: 31}, - end: {line: 2, column: 32}, - file: absoluteFrom('/root/path/relative/path.js'), - text: '2' - } + PH: {start: {line: 2, column: 26}, end: {line: 2, column: 27}, file, text: '1'}, + PH_1: {start: {line: 2, column: 31}, end: {line: 2, column: 32}, file, text: '2'} }, messagePartLocations: [ - { - start: {line: 2, column: 10}, - end: {line: 2, column: 24}, - file: absoluteFrom('/root/path/relative/path.js'), - text: ':@@custom-id:a' - }, - { - start: {line: 2, column: 28}, - end: {line: 2, column: 29}, - file: absoluteFrom('/root/path/relative/path.js'), - text: 'b' - }, - { - start: {line: 2, column: 33}, - end: {line: 2, column: 34}, - file: absoluteFrom('/root/path/relative/path.js'), - text: 'c' - } + {start: {line: 2, column: 10}, end: {line: 2, column: 24}, file, text: ':@@custom-id:a'}, + {start: {line: 2, column: 28}, end: {line: 2, column: 29}, file, text: 'b'}, + {start: {line: 2, column: 33}, end: {line: 2, column: 34}, file, text: 'c'} ], legacyIds: [], location: { diff --git a/packages/localize/src/tools/test/extract/integration/BUILD.bazel b/packages/localize/src/tools/test/extract/integration/BUILD.bazel index 7bbc57a8a8..0fbc08c302 100644 --- a/packages/localize/src/tools/test/extract/integration/BUILD.bazel +++ b/packages/localize/src/tools/test/extract/integration/BUILD.bazel @@ -15,6 +15,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/testing", "//packages/localize/src/tools", "//packages/localize/src/tools/test:test_lib", + "//packages/localize/src/tools/test/helpers", ], ) diff --git a/packages/localize/src/tools/test/extract/integration/main_spec.ts b/packages/localize/src/tools/test/extract/integration/main_spec.ts index fca98d89a7..982ee27210 100644 --- a/packages/localize/src/tools/test/extract/integration/main_spec.ts +++ b/packages/localize/src/tools/test/extract/integration/main_spec.ts @@ -7,15 +7,15 @@ */ 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 {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing'; import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing'; import {extractTranslations} from '../../../src/extract/main'; import {FormatOptions} from '../../../src/extract/translation_files/format_options'; +import {runInNativeFileSystem} from '../../helpers'; import {toAttributes} from '../translation_files/utils'; -runInEachFileSystem(() => { +runInNativeFileSystem(() => { let fs: FileSystem; let logger: MockLogger; let rootPath: AbsoluteFsPath; diff --git a/packages/localize/src/tools/test/extract/integration/test_files/BUILD.bazel b/packages/localize/src/tools/test/extract/integration/test_files/BUILD.bazel index 3abde62d10..a56e44bec8 100644 --- a/packages/localize/src/tools/test/extract/integration/test_files/BUILD.bazel +++ b/packages/localize/src/tools/test/extract/integration/test_files/BUILD.bazel @@ -1,6 +1,7 @@ package(default_visibility = ["//packages/localize/src/tools/test/extract/integration:__pkg__"]) load("@npm//typescript:index.bzl", "tsc") +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") tsc( name = "compile_es5", @@ -44,7 +45,8 @@ tsc( data = glob(["src/*.ts"]), ) -filegroup( +# Use copy_to_bin since filegroup doesn't seem to work on Windows. +copy_to_bin( name = "test_files", srcs = glob([ "**/*.js", diff --git a/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts b/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts index db587f0413..a1eb1b4b68 100644 --- a/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts +++ b/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts @@ -6,30 +6,42 @@ * 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} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {FileSystem, getFileSystem, PathSegment, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system'; import {ɵParsedMessage} from '@angular/localize/private'; import {transformSync} from '@babel/core'; import {makeEs5ExtractPlugin} from '../../../src/extract/source_files/es5_extract_plugin'; +import {runInNativeFileSystem} from '../../helpers'; + +runInNativeFileSystem(() => { + let fs: FileSystem; + let testPath: PathSegment; + + beforeEach(() => { + fs = getFileSystem(); + testPath = relativeFrom('app/dist/test.js'); + }); -runInEachFileSystem(() => { describe('makeEs5ExtractPlugin()', () => { it('should error with code-frame information if the first argument to `$localize` is not an array', () => { const input = '$localize(null, [])'; expect(() => transformCode(input)) .toThrowError( - 'Cannot create property \'message\' on string \'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' + + `Cannot create property 'message' on string '${testPath}: ` + + 'Unexpected messageParts for `$localize` (expected an array of strings).\n' + '> 1 | $localize(null, [])\n' + ' | ^^^^\''); }); function transformCode(input: string): ɵParsedMessage[] { const messages: ɵParsedMessage[] = []; + const cwd = fs.resolve('/'); + const filename = fs.resolve(cwd, testPath); transformSync(input, { plugins: [makeEs5ExtractPlugin(getFileSystem(), messages)], - filename: '/app/dist/test.js' + filename, + cwd, })!.code!; return messages; } diff --git a/packages/localize/src/tools/test/helpers/BUILD.bazel b/packages/localize/src/tools/test/helpers/BUILD.bazel new file mode 100644 index 0000000000..a54218c740 --- /dev/null +++ b/packages/localize/src/tools/test/helpers/BUILD.bazel @@ -0,0 +1,14 @@ +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "helpers", + testonly = True, + srcs = glob( + ["**/*.ts"], + ), + visibility = ["//packages/localize/src/tools/test:__subpackages__"], + deps = [ + "//packages/compiler-cli/src/ngtsc/file_system", + "//packages/compiler-cli/src/ngtsc/file_system/testing", + ], +) diff --git a/packages/localize/src/tools/test/helpers/index.ts b/packages/localize/src/tools/test/helpers/index.ts new file mode 100644 index 0000000000..9911cdd2b3 --- /dev/null +++ b/packages/localize/src/tools/test/helpers/index.ts @@ -0,0 +1,25 @@ +/** + * @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 {setFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; +import {InvalidFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/src/invalid_file_system'; +import {MockFileSystemNative} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; + +/** + * Only run these tests on the "native" file-system. + * + * Babel uses the `path.resolve()` function internally, which makes it very hard to mock out the + * file-system from the outside. We run these tests on Unix and Windows in our CI jobs, so there is + * test coverage. + */ +export function runInNativeFileSystem(callback: () => void) { + describe(`<>`, () => { + beforeEach(() => setFileSystem(new MockFileSystemNative())); + afterEach(() => setFileSystem(new InvalidFileSystem())); + callback(); + }); +} diff --git a/packages/localize/src/tools/test/source_file_utils_spec.ts b/packages/localize/src/tools/test/source_file_utils_spec.ts index 249c4beef8..4f36d9d9e7 100644 --- a/packages/localize/src/tools/test/source_file_utils_spec.ts +++ b/packages/localize/src/tools/test/source_file_utils_spec.ts @@ -6,16 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ import {absoluteFrom, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; import {ɵmakeTemplateObject} from '@angular/localize'; import {NodePath, TransformOptions, transformSync} from '@babel/core'; import generate from '@babel/generator'; - import template from '@babel/template'; import {Expression, Identifier, TaggedTemplateExpression, ExpressionStatement, CallExpression, isParenthesizedExpression, numericLiteral, binaryExpression, NumericLiteral} from '@babel/types'; -import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils'; -runInEachFileSystem(() => { +import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils'; +import {runInNativeFileSystem} from './helpers'; + +runInNativeFileSystem(() => { let fs: PathManipulation; beforeEach(() => fs = getFileSystem()); describe('utils', () => { @@ -414,7 +414,7 @@ runInEachFileSystem(() => { it('should return a plain object containing the start, end and file of a NodePath', () => { const taggedTemplate = getTaggedTemplate('const x = $localize `message`;', { filename: 'src/test.js', - sourceRoot: '/root', + sourceRoot: fs.resolve('/project'), }); const location = getLocation(fs, taggedTemplate)!; expect(location).toBeDefined(); @@ -422,12 +422,12 @@ runInEachFileSystem(() => { expect(location.start.constructor.name).toEqual('Object'); expect(location.end).toEqual({line: 0, column: 29}); expect(location.end?.constructor.name).toEqual('Object'); - expect(location.file).toEqual(absoluteFrom('/root/src/test.js')); + expect(location.file).toEqual(fs.resolve('/project/src/test.js')); }); it('should return `undefined` if the NodePath has no filename', () => { const taggedTemplate = getTaggedTemplate( - 'const x = $localize ``;', {sourceRoot: '/root', filename: undefined}); + 'const x = $localize ``;', {sourceRoot: fs.resolve('/project'), filename: undefined}); const location = getLocation(fs, taggedTemplate); expect(location).toBeUndefined(); }); @@ -451,7 +451,8 @@ function getExpressions( const expressions: NodePath[] = []; transformSync(code, { code: false, - filename: '/test/file.js', + filename: 'test/file.js', + cwd: '/', plugins: [{ visitor: { Expression: (path: NodePath) => { @@ -468,7 +469,8 @@ function getLocalizeCall(code: string): NodePath { let callPaths: NodePath[] = []; transformSync(code, { code: false, - filename: '/test/file.js', + filename: 'test/file.js', + cwd: '/', plugins: [{ visitor: { CallExpression(path) { diff --git a/packages/localize/src/tools/test/translate/asset_files/asset_file_translation_handler_spec.ts b/packages/localize/src/tools/test/translate/asset_files/asset_file_translation_handler_spec.ts index fa7fc32429..548e43dbf4 100644 --- a/packages/localize/src/tools/test/translate/asset_files/asset_file_translation_handler_spec.ts +++ b/packages/localize/src/tools/test/translate/asset_files/asset_file_translation_handler_spec.ts @@ -23,7 +23,7 @@ runInEachFileSystem(() => { beforeEach(() => { fs = getFileSystem(); - rootPath = absoluteFrom('/root/path'); + rootPath = absoluteFrom('/src/path'); filePath = relativeFrom('relative/path'); enTranslationPath = absoluteFrom('/translations/en/relative/path'); enUSTranslationPath = absoluteFrom('/translations/en-US/relative/path'); diff --git a/packages/localize/src/tools/test/translate/integration/BUILD.bazel b/packages/localize/src/tools/test/translate/integration/BUILD.bazel index ec88d4f317..f2fd7a622f 100644 --- a/packages/localize/src/tools/test/translate/integration/BUILD.bazel +++ b/packages/localize/src/tools/test/translate/integration/BUILD.bazel @@ -12,18 +12,17 @@ ts_library( "//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/testing", "//packages/localize/src/tools", + "//packages/localize/src/tools/test/helpers", ], ) jasmine_node_test( name = "integration", bootstrap = ["//tools/testing:node_no_angular_es5"], - data = glob( - [ - "locales/**", - "test_files/**", - ], - ), + data = [ + "//packages/localize/src/tools/test/translate/integration/locales", + "//packages/localize/src/tools/test/translate/integration/test_files", + ], deps = [ ":test_lib", "@npm//glob", diff --git a/packages/localize/src/tools/test/translate/integration/locales/BUILD.bazel b/packages/localize/src/tools/test/translate/integration/locales/BUILD.bazel new file mode 100644 index 0000000000..2a192a90ad --- /dev/null +++ b/packages/localize/src/tools/test/translate/integration/locales/BUILD.bazel @@ -0,0 +1,13 @@ +package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"]) + +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") + +# Use copy_to_bin since filegroup doesn't seem to work on Windows. +copy_to_bin( + name = "locales", + srcs = glob([ + "**/*.json", + "**/*.xlf", + "**/*.xtb", + ]), +) diff --git a/packages/localize/src/tools/test/translate/integration/main_spec.ts b/packages/localize/src/tools/test/translate/integration/main_spec.ts index fd8eb2ea9d..d091283820 100644 --- a/packages/localize/src/tools/test/translate/integration/main_spec.ts +++ b/packages/localize/src/tools/test/translate/integration/main_spec.ts @@ -6,15 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; -import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing'; import {resolve as realResolve} from 'path'; import {Diagnostics} from '../../../src/diagnostics'; import {translateFiles} from '../../../src/translate/main'; import {getOutputPathFn} from '../../../src/translate/output_path'; +import {runInNativeFileSystem} from '../../helpers'; -runInEachFileSystem(() => { +runInNativeFileSystem(() => { describe('translateFiles()', () => { let fs: FileSystem; let testDir: AbsoluteFsPath; @@ -33,7 +33,7 @@ runInEachFileSystem(() => { it('should copy non-code files to the destination folders', () => { const diagnostics = new Diagnostics(); - const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}')); + const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}')); translateFiles({ sourceRootPath: testFilesDir, sourceFilePaths: ['test-1.txt', 'test-2.txt'], @@ -69,7 +69,7 @@ runInEachFileSystem(() => { it('should translate and copy source-code files to the destination folders', () => { const diagnostics = new Diagnostics(); - const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}')); + const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}')); translateFiles({ sourceRootPath: testFilesDir, sourceFilePaths: ['test.js'], @@ -97,7 +97,7 @@ runInEachFileSystem(() => { it('should translate and copy source-code files overriding the locales', () => { const diagnostics = new Diagnostics(); - const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}')); + const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}')); translateFiles({ sourceRootPath: testFilesDir, sourceFilePaths: ['test.js'], @@ -131,7 +131,7 @@ runInEachFileSystem(() => { it('should merge translation files, if more than one provided, and translate source-code', () => { const diagnostics = new Diagnostics(); - const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}')); + const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}')); translateFiles({ sourceRootPath: testFilesDir, sourceFilePaths: ['test-extra.js'], @@ -165,7 +165,7 @@ runInEachFileSystem(() => { it('should transform and/or copy files to the destination folders', () => { const diagnostics = new Diagnostics(); - const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}')); + const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}')); translateFiles({ sourceRootPath: testFilesDir, sourceFilePaths: ['test-1.txt', 'test-2.txt', 'test.js'], diff --git a/packages/localize/src/tools/test/translate/integration/test_files/BUILD.bazel b/packages/localize/src/tools/test/translate/integration/test_files/BUILD.bazel new file mode 100644 index 0000000000..9e8dfcfa8f --- /dev/null +++ b/packages/localize/src/tools/test/translate/integration/test_files/BUILD.bazel @@ -0,0 +1,12 @@ +package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"]) + +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") + +# Use copy_to_bin since filegroup doesn't seem to work on Windows. +copy_to_bin( + name = "test_files", + srcs = glob([ + "**/*.js", + "**/*.txt", + ]), +) diff --git a/packages/localize/src/tools/test/translate/output_path_spec.ts b/packages/localize/src/tools/test/translate/output_path_spec.ts index 37f0024db7..377a5009b0 100644 --- a/packages/localize/src/tools/test/translate/output_path_spec.ts +++ b/packages/localize/src/tools/test/translate/output_path_spec.ts @@ -5,36 +5,39 @@ * 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 {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system'; +import {absoluteFrom, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system'; import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; import {getOutputPathFn} from '../../src/translate/output_path'; runInEachFileSystem(() => { + let fs: PathManipulation; + beforeEach(() => fs = getFileSystem()); + describe('getOutputPathFn()', () => { it('should return a function that joins the `outputPath` and the `relativePath`', () => { - const fn = getOutputPathFn(absoluteFrom('/output/path')); + const fn = getOutputPathFn(fs, absoluteFrom('/output/path')); expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/path/relative/path')); expect(fn('en', '../parent/path')).toEqual(absoluteFrom('/output/parent/path')); }); it('should return a function that interpolates the `{{LOCALE}}` in the middle of the `outputPath`', () => { - const fn = getOutputPathFn(absoluteFrom('/output/{{LOCALE}}/path')); + const fn = getOutputPathFn(fs, absoluteFrom('/output/{{LOCALE}}/path')); expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/en/path/relative/path')); expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output/fr/path/relative/path')); }); it('should return a function that interpolates the `{{LOCALE}}` in the middle of a path segment in the `outputPath`', () => { - const fn = getOutputPathFn(absoluteFrom('/output-{{LOCALE}}-path')); + const fn = getOutputPathFn(fs, absoluteFrom('/output-{{LOCALE}}-path')); expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output-en-path/relative/path')); expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output-fr-path/relative/path')); }); it('should return a function that interpolates the `{{LOCALE}}` at the end of the `outputPath`', () => { - const fn = getOutputPathFn(absoluteFrom('/output/{{LOCALE}}')); + const fn = getOutputPathFn(fs, absoluteFrom('/output/{{LOCALE}}')); expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/en/relative/path')); expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output/fr/relative/path')); }); diff --git a/packages/localize/src/tools/test/translate/source_files/es2015_translate_plugin_spec.ts b/packages/localize/src/tools/test/translate/source_files/es2015_translate_plugin_spec.ts index b224924511..3e3b5300db 100644 --- a/packages/localize/src/tools/test/translate/source_files/es2015_translate_plugin_spec.ts +++ b/packages/localize/src/tools/test/translate/source_files/es2015_translate_plugin_spec.ts @@ -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 {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize'; import {ɵParsedTranslation} from '@angular/localize/private'; import {transformSync} from '@babel/core'; @@ -13,8 +13,15 @@ import {transformSync} from '@babel/core'; import {Diagnostics} from '../../../src/diagnostics'; import {TranslatePluginOptions} from '../../../src/source_file_utils'; import {makeEs2015TranslatePlugin} from '../../../src/translate/source_files/es2015_translate_plugin'; +import {runInNativeFileSystem} from '../../helpers'; + +runInNativeFileSystem(() => { + let fs: FileSystem; + + beforeEach(() => { + fs = getFileSystem(); + }); -runInEachFileSystem(() => { describe('makeEs2015Plugin', () => { describe('(no translations)', () => { it('should transform `$localize` tags with binary expression', () => { @@ -172,9 +179,12 @@ runInEachFileSystem(() => { function transformCode( input: string, translations: Record = {}, pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string { + const cwd = fs.resolve('/'); + const filename = fs.resolve(cwd, 'app/dist/test.js'); return transformSync(input, { plugins: [makeEs2015TranslatePlugin(diagnostics, translations, pluginOptions)], - filename: '/app/dist/test.js' + filename, + cwd, })!.code!; } }); diff --git a/packages/localize/src/tools/test/translate/source_files/es5_translate_plugin_spec.ts b/packages/localize/src/tools/test/translate/source_files/es5_translate_plugin_spec.ts index c89294b5e8..a562c250e3 100644 --- a/packages/localize/src/tools/test/translate/source_files/es5_translate_plugin_spec.ts +++ b/packages/localize/src/tools/test/translate/source_files/es5_translate_plugin_spec.ts @@ -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 {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {FileSystem, getFileSystem, PathSegment, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system'; import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize'; import {ɵParsedTranslation} from '@angular/localize/private'; import {transformSync} from '@babel/core'; @@ -13,8 +13,17 @@ import {transformSync} from '@babel/core'; import {Diagnostics} from '../../../src/diagnostics'; import {TranslatePluginOptions} from '../../../src/source_file_utils'; import {makeEs5TranslatePlugin} from '../../../src/translate/source_files/es5_translate_plugin'; +import {runInNativeFileSystem} from '../../helpers'; + +runInNativeFileSystem(() => { + let fs: FileSystem; + let testPath: PathSegment; + + beforeEach(() => { + fs = getFileSystem(); + testPath = relativeFrom('app/dist/test.js'); + }); -runInEachFileSystem(() => { describe('makeEs5Plugin', () => { describe('(no translations)', () => { it('should transform `$localize` calls with binary expression', () => { @@ -142,7 +151,7 @@ runInEachFileSystem(() => { expect(diagnostics.hasErrors).toBe(true); expect(diagnostics.messages[0]).toEqual({ type: 'error', - message: '/app/dist/test.js: `$localize` called without any arguments.\n' + + message: `${testPath}: \`$localize\` called without any arguments.\n` + '> 1 | $localize()\n' + ' | ^^^^^^^^^^^', }); @@ -156,8 +165,7 @@ runInEachFileSystem(() => { expect(diagnostics.hasErrors).toBe(true); expect(diagnostics.messages[0]).toEqual({ type: 'error', - message: - '/app/dist/test.js: Unexpected argument to `$localize` (expected an array).\n' + + message: `${testPath}: Unexpected argument to \`$localize\` (expected an array).\n` + '> 1 | $localize(...x)\n' + ' | ^^^^', }); @@ -172,7 +180,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' + + `${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` + '> 1 | $localize(null, [])\n' + ' | ^^^^', }); @@ -187,7 +195,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Unexpected `raw` argument to the "makeTemplateObject()" function (expected an expression).\n' + + `${testPath}: Unexpected \`raw\` argument to the "makeTemplateObject()" function (expected an expression).\n` + '> 1 | $localize(__makeTemplateObject([], ...[]))\n' + ' | ^^^^^', }); @@ -202,7 +210,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Unexpected `cooked` argument to the "makeTemplateObject()" function (expected an expression).\n' + + `${testPath}: Unexpected \`cooked\` argument to the "makeTemplateObject()" function (expected an expression).\n` + '> 1 | $localize(__makeTemplateObject(...[], []))\n' + ' | ^^^^^', }); @@ -217,7 +225,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' + + `${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` + '> 1 | $localize(__makeTemplateObject(["a", 12, "b"], ["a", "12", "b"]))\n' + ' | ^^^^^^^^^^^^^^', }); @@ -232,7 +240,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' + + `${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` + '> 1 | $localize(__makeTemplateObject(["a", "12", "b"], ["a", 12, "b"]))\n' + ' | ^^^^^^^^^^^^^^', }); @@ -247,7 +255,7 @@ runInEachFileSystem(() => { expect(diagnostics.messages[0]).toEqual({ type: 'error', message: - '/app/dist/test.js: Invalid substitutions for `$localize` (expected all substitution arguments to be expressions).\n' + + `${testPath}: Invalid substitutions for \`$localize\` (expected all substitution arguments to be expressions).\n` + '> 1 | $localize(__makeTemplateObject(["a", "b"], ["a", "b"]), ...[])\n' + ' | ^^^^^', }); @@ -361,9 +369,12 @@ runInEachFileSystem(() => { function transformCode( input: string, translations: Record = {}, pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string { + const cwd = fs.resolve('/'); + const filename = fs.resolve(cwd, testPath); return transformSync(input, { plugins: [makeEs5TranslatePlugin(diagnostics, translations, pluginOptions)], - filename: '/app/dist/test.js' + filename, + cwd, })!.code!; } }); diff --git a/packages/localize/src/tools/test/translate/source_files/source_file_translation_handler_spec.ts b/packages/localize/src/tools/test/translate/source_files/source_file_translation_handler_spec.ts index c8a09c4c57..aa9ae1dbd9 100644 --- a/packages/localize/src/tools/test/translate/source_files/source_file_translation_handler_spec.ts +++ b/packages/localize/src/tools/test/translate/source_files/source_file_translation_handler_spec.ts @@ -23,7 +23,7 @@ runInEachFileSystem(() => { beforeEach(() => { fs = getFileSystem(); - rootPath = absoluteFrom('/root/path'); + rootPath = absoluteFrom('/src/path'); filePath = relativeFrom('relative/path.js'); enTranslationPath = absoluteFrom('/translations/en/relative/path.js'); enUSTranslationPath = absoluteFrom('/translations/en-US/relative/path.js');