Skip to content

Commit 455c130

Browse files
authored
feat(compiler): experimentalErrorRecoveryMode for template transformation (#5582)
1 parent 7d334c7 commit 455c130

File tree

2 files changed

+74
-15
lines changed

2 files changed

+74
-15
lines changed

packages/@lwc/compiler/src/transformers/__tests__/transform-html.spec.ts

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { APIVersion, noop } from '@lwc/shared';
99
import { transformSync } from '../transformer';
1010
import type { TransformOptions } from '../../options';
1111

12-
const TRANSFORMATION_OPTIONS = {
12+
const BASE_TRANSFORM_OPTIONS = {
1313
namespace: 'x',
1414
name: 'foo',
1515
} satisfies TransformOptions;
1616

1717
describe('transformSync', () => {
1818
it('should throw when processing an invalid HTML file', () => {
19-
expect(() => transformSync(`<html`, 'foo.html', TRANSFORMATION_OPTIONS)).toThrow(
19+
expect(() => transformSync(`<html`, 'foo.html', BASE_TRANSFORM_OPTIONS)).toThrow(
2020
'Invalid HTML syntax: eof-in-tag.'
2121
);
2222
});
@@ -27,7 +27,7 @@ describe('transformSync', () => {
2727
<div>Hello</div>
2828
</template>
2929
`;
30-
const { code } = transformSync(template, 'foo.html', TRANSFORMATION_OPTIONS);
30+
const { code } = transformSync(template, 'foo.html', BASE_TRANSFORM_OPTIONS);
3131

3232
expect(code).toContain(`tmpl.stylesheets = [];`);
3333
});
@@ -75,7 +75,7 @@ describe('transformSync', () => {
7575
it.for(configs)('$name', ({ config, expected }) => {
7676
const template = `<template><img src="http://example.com/img.png" crossorigin="anonymous"></template>`;
7777
const { code, warnings } = transformSync(template, 'foo.html', {
78-
...TRANSFORMATION_OPTIONS,
78+
...BASE_TRANSFORM_OPTIONS,
7979
...config,
8080
});
8181
expect(warnings!.length).toBe(0);
@@ -106,7 +106,7 @@ describe('transformSync', () => {
106106
},
107107
],
108108
},
109-
...TRANSFORMATION_OPTIONS,
109+
...BASE_TRANSFORM_OPTIONS,
110110
});
111111
expect(warnings).toHaveLength(0);
112112
expect(code).toMatch('renderer: renderer');
@@ -120,7 +120,7 @@ describe('transformSync', () => {
120120
</svg>
121121
</template>
122122
`;
123-
const { code, warnings } = transformSync(template, 'foo.html', TRANSFORMATION_OPTIONS);
123+
const { code, warnings } = transformSync(template, 'foo.html', BASE_TRANSFORM_OPTIONS);
124124
expect(warnings).toHaveLength(0);
125125
expect(code).not.toMatch('renderer: renderer');
126126
});
@@ -131,7 +131,7 @@ describe('transformSync', () => {
131131
<div>Hello</div>
132132
</template>
133133
`;
134-
const { cssScopeTokens } = transformSync(template, 'foo.html', TRANSFORMATION_OPTIONS);
134+
const { cssScopeTokens } = transformSync(template, 'foo.html', BASE_TRANSFORM_OPTIONS);
135135

136136
expect(cssScopeTokens!.sort()).toEqual([
137137
'lwc-1hl7358i549',
@@ -150,7 +150,7 @@ describe('transformSync', () => {
150150
`;
151151
const { code, warnings } = transformSync(template, 'foo.html', {
152152
enableDynamicComponents: true,
153-
...TRANSFORMATION_OPTIONS,
153+
...BASE_TRANSFORM_OPTIONS,
154154
});
155155

156156
expect(warnings).toHaveLength(0);
@@ -166,7 +166,7 @@ describe('transformSync', () => {
166166
expect(() =>
167167
transformSync(template, 'foo.html', {
168168
enableDynamicComponents: false,
169-
...TRANSFORMATION_OPTIONS,
169+
...BASE_TRANSFORM_OPTIONS,
170170
})
171171
).toThrow('LWC1188: Invalid dynamic components usage');
172172
});
@@ -179,7 +179,7 @@ describe('transformSync', () => {
179179
`;
180180
const { code, warnings } = transformSync(template, 'foo.html', {
181181
experimentalDynamicDirective: true,
182-
...TRANSFORMATION_OPTIONS,
182+
...BASE_TRANSFORM_OPTIONS,
183183
});
184184

185185
expect(warnings).toHaveLength(1);
@@ -198,7 +198,7 @@ describe('transformSync', () => {
198198
expect(() =>
199199
transformSync(template, 'foo.html', {
200200
experimentalDynamicDirective: false,
201-
...TRANSFORMATION_OPTIONS,
201+
...BASE_TRANSFORM_OPTIONS,
202202
})
203203
).toThrowErrorMatchingInlineSnapshot(
204204
`[Error: LWC1128: Invalid lwc:dynamic usage. The LWC dynamic directive must be enabled in order to use this feature.]`
@@ -219,7 +219,7 @@ describe('transformSync', () => {
219219
incrementCounter,
220220
},
221221
experimentalDynamicDirective: true,
222-
...TRANSFORMATION_OPTIONS,
222+
...BASE_TRANSFORM_OPTIONS,
223223
});
224224

225225
const calls = incrementCounter.mock.calls;
@@ -228,4 +228,54 @@ describe('transformSync', () => {
228228
expect(calls[1][0]).toBe('lwc-dynamic-directive');
229229
});
230230
});
231+
232+
describe('experimentalErrorRecoveryMode', () => {
233+
const transformOptions = {
234+
...BASE_TRANSFORM_OPTIONS,
235+
experimentalErrorRecoveryMode: true,
236+
};
237+
238+
it('should apply transformation for template file', () => {
239+
const template = `
240+
<template>
241+
<div>Hello</div>
242+
</template>
243+
`;
244+
245+
const { code, warnings } = transformSync(template, 'foo.html', transformOptions);
246+
247+
expect(code).toContain(`tmpl.stylesheets = [];`);
248+
expect(warnings?.length).toBe(0);
249+
});
250+
251+
it('should throw an AggregateError', () => {
252+
const templateWithErrors = `
253+
<template>
254+
<div lwc:invalid-directive="test"></div>
255+
<span lwc:another-invalid="test"></span>
256+
<p lwc:yet-another-invalid="test"></p>
257+
</template>
258+
`;
259+
expect(() => {
260+
transformSync(templateWithErrors, 'foo.html', transformOptions);
261+
}).toThrowAggregateError([
262+
'LWC1127: Invalid directive "lwc:invalid-directive" on element <div>.',
263+
'LWC1127: Invalid directive "lwc:another-invalid" on element <span>.',
264+
'LWC1127: Invalid directive "lwc:yet-another-invalid" on element <p>.',
265+
]);
266+
});
267+
268+
it('should aggregate errors from custom element types', () => {
269+
const templateWithErrors = `
270+
<template>
271+
<custom-element lwc:invalid="test"></custom-element>
272+
</template>
273+
`;
274+
expect(() => {
275+
transformSync(templateWithErrors, 'foo.html', transformOptions);
276+
}).toThrowAggregateError([
277+
'LWC1127: Invalid directive "lwc:invalid" on element <custom-element>.',
278+
]);
279+
});
280+
});
231281
});

packages/@lwc/compiler/src/transformers/template.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
normalizeToCompilerError,
1010
DiagnosticLevel,
1111
TransformerErrors,
12+
CompilerAggregateError,
1213
} from '@lwc/errors';
1314
import { compile } from '@lwc/template-compiler';
1415

@@ -46,6 +47,7 @@ export default function templateTransform(
4647
name,
4748
apiVersion,
4849
disableSyntheticShadowSupport,
50+
experimentalErrorRecoveryMode,
4951
} = options;
5052
const experimentalDynamicDirective =
5153
deprecatedDynamicDirective ?? Boolean(experimentalDynamicComponent);
@@ -71,9 +73,16 @@ export default function templateTransform(
7173
throw normalizeToCompilerError(TransformerErrors.HTML_TRANSFORMER_ERROR, e, { filename });
7274
}
7375

74-
const fatalError = result.warnings.find((warning) => warning.level === DiagnosticLevel.Error);
75-
if (fatalError) {
76-
throw CompilerError.from(fatalError, { filename });
76+
const errors = result.warnings.filter((warning) => warning.level === DiagnosticLevel.Error);
77+
78+
if (experimentalErrorRecoveryMode && errors.length > 0) {
79+
throw new CompilerAggregateError(
80+
errors.map((err) => CompilerError.from(err, { filename }))
81+
);
82+
}
83+
84+
if (errors[0]) {
85+
throw CompilerError.from(errors[0], { filename });
7786
}
7887

7988
// The "Error" diagnostic level makes no sense to include here, because it would already have been

0 commit comments

Comments
 (0)