Skip to content
This repository was archived by the owner on May 4, 2020. It is now read-only.

Commit 7539936

Browse files
author
Long Ho
committed
feat(@formatjs/cli): add --throws option to prevent throwing on a single file
1 parent 0603577 commit 7539936

File tree

6 files changed

+72
-71
lines changed

6 files changed

+72
-71
lines changed

packages/babel-plugin-react-intl/src/options.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface OptionsSchema {
99
moduleSourceName?: string;
1010
extractSourceLocation?: boolean;
1111
messagesDir?: string;
12-
overrideIdFn?: (id: string, defaultMessage: string, descriptor: string, filePath: string) => string;
12+
overrideIdFn?: (id: string, defaultMessage: string, description: string, filePath: string) => string;
1313
removeDefaultMessage?: boolean;
1414
extractFromFormatMessageCall?: boolean;
1515
additionalComponentNames?: string[];

packages/cli/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@ Options:
4343
description: 'baz'})` (default: false)
4444
--ignore List of glob paths to **not** extract translations from.
4545
--output-empty-json Output file with empty [] if src has no messages. For build systems like bazel that relies on specific output mapping, not writing out a file can cause issues.
46+
--throws Whether to throw an exception when we fail to process any file in the batch.
4647
-h, --help output usage information
4748
```

packages/cli/src/cli.ts

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ async function main(argv: string[]) {
107107
'--ignore <files>',
108108
'List of glob paths to **not** extract translations from.'
109109
)
110+
.option(
111+
'--throws',
112+
'Whether to throw an exception when we fail to process any file in the batch.'
113+
)
110114
.action(async (files: readonly string[], cmdObj: ExtractCLIOptions) => {
111115
const processedFiles = [];
112116
for (const f of files) {

packages/cli/src/extract.ts

+59-66
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ import {outputJSONSync} from 'fs-extra';
77
import {interpolateName} from 'loader-utils';
88
import {IOptions as GlobOptions} from 'glob';
99

10-
export type ExtractCLIOptions = Omit<OptionsSchema, 'overrideIdFn'> & {
10+
export type ExtractCLIOptions = Omit<ExtractOptions, 'overrideIdFn'> & {
1111
outFile?: string;
12-
idInterpolationPattern?: string;
1312
ignore?: GlobOptions['ignore'];
1413
};
1514

1615
export type ExtractOptions = OptionsSchema & {
16+
throws?: boolean;
1717
idInterpolationPattern?: string;
18-
ignore?: GlobOptions['ignore'];
1918
};
2019

2120
function getBabelConfig(
@@ -62,16 +61,6 @@ function getBabelConfig(
6261
};
6362
}
6463

65-
function extractSingleFile(
66-
filename: string,
67-
extractOptions: ExtractCLIOptions
68-
): babel.BabelFileResult | null {
69-
return babel.transformFileSync(
70-
filename,
71-
getBabelConfig(extractOptions, {filename})
72-
);
73-
}
74-
7564
function getReactIntlMessages(
7665
babelResult: babel.BabelFileResult | null
7766
): Record<string, ExtractedMessageDescriptor> {
@@ -87,58 +76,64 @@ function getReactIntlMessages(
8776

8877
export async function extract(
8978
files: readonly string[],
90-
{idInterpolationPattern, ...babelOpts}: ExtractOptions
79+
{idInterpolationPattern, throws, ...babelOpts}: ExtractOptions
9180
) {
92-
let extractedMessages: Record<string, ExtractedMessageDescriptor> = {};
93-
9481
if (files.length > 0) {
95-
for (const file of files) {
96-
if (!babelOpts.overrideIdFn && idInterpolationPattern) {
97-
babelOpts = {
98-
...babelOpts,
99-
overrideIdFn: (id, defaultMessage, description) =>
100-
id ||
101-
interpolateName(
102-
{
103-
resourcePath: file,
104-
} as any,
105-
idInterpolationPattern,
106-
{content: `${defaultMessage}#${description}`}
107-
),
108-
};
109-
}
110-
const babelResult = extractSingleFile(file, babelOpts);
111-
const singleFileExtractedMessages = getReactIntlMessages(babelResult);
112-
113-
Object.assign(extractedMessages, singleFileExtractedMessages);
114-
}
115-
} else {
116-
if (files.length === 0 && process.stdin.isTTY) {
117-
warn('Reading source file from TTY.');
118-
}
119-
if (!babelOpts.overrideIdFn && idInterpolationPattern) {
120-
babelOpts = {
121-
...babelOpts,
122-
overrideIdFn: (id, defaultMessage, description) =>
123-
id ||
124-
interpolateName(
125-
{
126-
resourcePath: 'dummy',
127-
} as any,
128-
idInterpolationPattern,
129-
{content: `${defaultMessage}#${description}`}
130-
),
131-
};
132-
}
133-
const stdinSource = await getStdinAsString();
134-
const babelResult = babel.transformSync(
135-
stdinSource,
136-
getBabelConfig(babelOpts)
82+
const results = await Promise.all(
83+
files.map(filename => {
84+
if (!babelOpts.overrideIdFn && idInterpolationPattern) {
85+
babelOpts = {
86+
...babelOpts,
87+
overrideIdFn: (id, defaultMessage, description) =>
88+
id ||
89+
interpolateName(
90+
{
91+
resourcePath: filename,
92+
} as any,
93+
idInterpolationPattern,
94+
{content: `${defaultMessage}#${description}`}
95+
),
96+
};
97+
}
98+
const promise = babel.transformFileAsync(
99+
filename,
100+
getBabelConfig(babelOpts, {filename: filename})
101+
);
102+
return throws ? promise : promise.catch(e => warn(e));
103+
})
137104
);
138-
139-
extractedMessages = getReactIntlMessages(babelResult);
105+
return Object.values(
106+
results.reduce(
107+
(all, babelResult) =>
108+
babelResult ? {...all, ...getReactIntlMessages(babelResult)} : all,
109+
{} as Record<string, ExtractedMessageDescriptor>
110+
)
111+
);
112+
}
113+
if (files.length === 0 && process.stdin.isTTY) {
114+
warn('Reading source file from TTY.');
115+
}
116+
if (!babelOpts.overrideIdFn && idInterpolationPattern) {
117+
babelOpts = {
118+
...babelOpts,
119+
overrideIdFn: (id, defaultMessage, description) =>
120+
id ||
121+
interpolateName(
122+
{
123+
resourcePath: 'dummy',
124+
} as any,
125+
idInterpolationPattern,
126+
{content: `${defaultMessage}#${description}`}
127+
),
128+
};
140129
}
141-
return Object.values(extractedMessages);
130+
const stdinSource = await getStdinAsString();
131+
const babelResult = babel.transformSync(
132+
stdinSource,
133+
getBabelConfig(babelOpts)
134+
);
135+
136+
return Object.values(getReactIntlMessages(babelResult));
142137
}
143138

144139
export default async function extractAndWrite(
@@ -149,17 +144,15 @@ export default async function extractAndWrite(
149144
if (outFile) {
150145
extractOpts.messagesDir = undefined;
151146
}
152-
const extractedMessages = extract(files, extractOpts);
147+
const extractedMessages = await extract(files, extractOpts);
153148
const printMessagesToStdout = extractOpts.messagesDir == null && !outFile;
154149
if (outFile) {
155-
outputJSONSync(outFile, Object.values(extractedMessages), {
150+
outputJSONSync(outFile, extractedMessages, {
156151
spaces: 2,
157152
});
158153
}
159154
if (printMessagesToStdout) {
160-
process.stdout.write(
161-
JSON.stringify(Object.values(extractedMessages), null, 2)
162-
);
155+
process.stdout.write(JSON.stringify(extractedMessages, null, 2));
163156
process.stdout.write('\n');
164157
}
165158
}

packages/cli/tests/extract/integration_tests/__snapshots__/index.test.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ Options:
229229
--extract-from-format-message-call Opt-in to extract from \`intl.formatMessage\` call with the same restrictions, e.g: has to be called with object literal such as \`intl.formatMessage({ id: 'foo', defaultMessage: 'bar', description: 'baz'})\` (default: false)
230230
--output-empty-json Output file with empty [] if src has no messages. For build systems like bazel that relies on specific output mapping, not writing out a file can cause issues. (default: false)
231231
--ignore <files> List of glob paths to **not** extract translations from.
232+
--throws Whether to throw an exception when we fail to process any file in the batch.
232233
-h, --help display help for command
233234
"
234235
`;

packages/cli/tests/extract/unit.test.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ jest.mock('@babel/core', () => {
1212
return {
1313
__esModule: true,
1414
transformSync: jest.fn().mockReturnValue(mockBabelResult),
15-
transformFileSync: jest.fn().mockReturnValue(mockBabelResult),
15+
transformFileAsync: jest
16+
.fn()
17+
.mockReturnValue(Promise.resolve(mockBabelResult)),
1618
};
1719
});
1820

@@ -52,8 +54,8 @@ test('it passes camelCase-converted arguments to babel API', () => {
5254
additionalComponentNames: ['Foo', 'Bar'],
5355
};
5456

55-
expect(babel.transformFileSync).toHaveBeenCalledTimes(2);
56-
expect(babel.transformFileSync).toHaveBeenNthCalledWith(
57+
expect(babel.transformFileAsync).toHaveBeenCalledTimes(2);
58+
expect(babel.transformFileAsync).toHaveBeenNthCalledWith(
5759
1,
5860
'file1.js',
5961
expect.objectContaining({
@@ -70,7 +72,7 @@ test('it passes camelCase-converted arguments to babel API', () => {
7072
],
7173
})
7274
);
73-
expect(babel.transformFileSync).toHaveBeenNthCalledWith(
75+
expect(babel.transformFileAsync).toHaveBeenNthCalledWith(
7476
2,
7577
'file2.tsx',
7678
expect.objectContaining({

0 commit comments

Comments
 (0)