Skip to content

Conversation

@rax-it
Copy link
Contributor

@rax-it rax-it commented Oct 21, 2025

Details

This change introduces a transformOption: experimentalErrorRecoveryMode which also sets babel parsers errorRecovery flag.
This changes lwc compilers, specifically babel-plugin-component behavior from

Throw at FIRST Error --> Collect as MANY errors as we can in a single invocation

While in theory we might still get errors in subsequent compile requests as some validation paths were skipped because we couldn't continue in a meaningful way.

These errors are then put in-to a bag of errors, {metadata: { lwcErrors: CompilerError[]}}, upon completion of the transform process our transformer looks for presence of these errors and throws an AggregateError.

Currently, only scriptTransformer is updated to utilize this behavior with other transformers (html, css) to follow suit.
experimentalErrorRecoveryMode defaults to false hence there should be no observable changes in current compilation scenarios.

Does this pull request introduce a breaking change?

  • 😮‍💨 No, it does not introduce a breaking change.

Does this pull request introduce an observable change?

  • 🤞 No, it does not introduce an observable change.

GUS work item

W-19992686

@rax-it rax-it marked this pull request as ready for review October 23, 2025 19:56
@rax-it rax-it requested a review from a team as a code owner October 23, 2025 19:56

expect(() => {
transformSync(actual, 'foo.js', TRANSFORM_OPTIONS);
}).toThrowAggregateError(['LWC1107: Invalid property name "dataInvalidProperty".']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}).toThrowAggregateError(['LWC1107: Invalid property name "dataInvalidProperty".']);
}).toThrow(aggregatErrorMatcher(['LWC1107: Invalid property name "dataInvalidProperty".']));

We can use asymmetric matchers to avoid having to a whole new thing.

function aggregateErrorMatcher(messages: string[]) {
    return expect.objectContaining({
        name: 'AggregateError',
        message: 'Multiple errors occurred during compilation.',
        errors: messages.map((msg) => {
            return expect.objectContaining({
                name: 'Error',
                message: expect.stringContaining(msg),
            });
        }),
    });
}

Copy link
Contributor

@jye-sf jye-sf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning for putting this as its own separate lwcErrors vs the errors parameter?

name = (expressionPath.node.callee as types.V8IntrinsicIdentifier).name as LwcDecoratorName;
} else {
throw generateInvalidDecoratorError(decoratorPath, state);
//TODO: Add a fallback assignment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what you mean by fallback assignment here?

}

let result;
const extractLwcErrors = (result: babel.BabelFileResult): CompilerDiagnostic[] => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this being an inline function rather than just defined as a separate function or used inline? It's a bit far from where it's used, and it's only ever used once. Feels like inline would be cleaner and let you combine some of the code blocks and flag checks.

},
})!;

const lwcErrors = extractLwcErrors(result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See question above about this being a separate function. Feels like you could combine the code below with the code in the function and get cleaner conditional blocks?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially even combine the code after the try/catch block here too.

const lwcErrors = extractLwcErrors(result);

if (!experimentalErrorRecoveryMode || lwcErrors.length === 0) {
return {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I personally prefer having the return statement at the end even if it means result isn't a const. It forces a cleaner separation between the 'successful' codepath and the 'error' code paths, and doesn't hide the main successful return value in the middle.

errors.push(...lwcErrors.map((diagnostic) => CompilerError.from(diagnostic)));
} catch (e) {
// If we are here in errorRecoveryMode then it's most likely that we have run into
// an unforeseen error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we don't want to try to aggregate any 'thrown' errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only happen when babel.transformSync call throws, compiler-plugin errors should not cause the compilation itself to fail. This will be an instance of Error and we don't have our "lwcErrors" bag of errors , hence nothing to Aggregate this error to.

This also means that when running in errorRecoveryMode compiler can now throw 2 types of errors CompilerError and an AggregateError but I think that might me fine, unless we need to converge them to a single error type for some reason.
Let me know if you have any thoughts here?

): types.ObjectProperty[] {
const properties = wireConfig.get('properties');

// Should only occurs in error recovery mode when config validation has already failed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Should only occurs in error recovery mode when config validation has already failed
// Should only occur in error recovery mode when config validation has already failed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what does this mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This essentially means that we have reached a path that we shouldn't have reached given the existing constraints/validations in place.

Ex: we reach wire.transfrom only when we have successfully gathered metadata, now say if we failed to identify the decorator type, instead of throwing we just log that error and continue to parse but this causes an issue here where we are expecting wireConfig.get('properties') to be an Array but instead it could be anything like string, undefined.

So to prevent compilation from breaking unexpectedly we just return an skip processing these props in errorRecoveryMode since we know this path already has known issues and is going to fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants