X Tutup
The Wayback Machine - https://web.archive.org/web/20220105002606/https://github.com/angular/angular/pull/44001
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(compiler): show meaningful diagnostic when reporting a template error fails #44001

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

@JoostK
Copy link
Member

@JoostK JoostK commented Oct 31, 2021

When the Angular compiler emits a diagnostic in a template file, it
forces TypeScript to parse that template. Templates are not TypeScript,
so this parse finds a bunch of parsing errors, which Angular then
ignores and we show the diagnostic anyways because we have more context.
This can lead to strange behavior in TypeScript because templates are so
weird that it can break the parser and crash the whole compiler.

For example, certain Angular templates can encounter failures fixed by
microsoft/TypeScript#45987, which are not easily debuggable and require
a TS upgrade to fix.

This commit introduces logic to handle the error gracefully, by falling
back to report the template error on the component class itself. The
diagnostic is extended to still reference the template location and
includes the failure's stack trace, to allow the parsing failure to be
reported to TypeScript (as parsing should in theory not cause a crash).

Closes #43970


@dgp1130 PTAL, curious to hear if you think this is sufficient or if you have suggestions to improve it.

@ngbot ngbot bot added this to the Backlog milestone Oct 31, 2021
@google-cla google-cla bot added the cla: yes label Oct 31, 2021
@JoostK JoostK marked this pull request as ready for review Oct 31, 2021
@JoostK JoostK requested a review from dgp1130 Oct 31, 2021
@JoostK JoostK force-pushed the ngtsc/degraded-template-diags branch from 4349396 to 6c42323 Oct 31, 2021
Copy link
Contributor

@dgp1130 dgp1130 left a comment

Thanks for doing this Joost!

parseTemplateAsSourceFileForTest = null;
}

function parseTemplateAsSourceFile(fileName: string, template: string): ts.SourceFile {
Copy link
Contributor

@dgp1130 dgp1130 Nov 1, 2021

Choose a reason for hiding this comment

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

Instead of this fake function with set*() and reset*() functions, could we just use Jasmine spies for that? It's effectively the same thing, but the test framework handles all the setup and restore functionality.

Copy link
Member Author

@JoostK JoostK Nov 24, 2021

Choose a reason for hiding this comment

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

I am not sure how to achieve that nicely in this case, TBH. Spying on ts.createSourceFile is problematic in the sense that the test calls into that function multiple times, so we'd have to conditionally call through which I don't like. As for parseTemplateAsSourceFile, even if it becomes exported will Jasmine be able to spy on the ES export? That sounds sketchy to me.

Copy link
Contributor

@dgp1130 dgp1130 Nov 24, 2021

Choose a reason for hiding this comment

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

Spying on an internal function is a bit awkward, but set*() and reset*() are already exported to work around this, so I'm not sure it's that much worse.

If ts.createSourceFile() is too noisy, then we could make parseTemplateAsSourceFile() just call through:

export function parseTemplateAsSourceFile(fileName: string, template: string): ts.SourceFile {
  return ts.createSourceFile(fileName, template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
}

We could put this in another file on its own so as not to pollute the public API of diagnostic.ts and then it can be spied on in the test.

I haven't tried Jasmine spies with real ES modules, so I'm not sure if there are caveats to it. You may need to import * as foo from '...'; so the spy will pick up the call. I still think that's an improvement over putting multiple test-only functions into the prod build and inventing our own mocking semantics for this file.

// diagnostic.ts
import * as templateParser from './parse_template';

export function makeTemplateDiagnostic(/* ... */): TemplateDiagnostic {
  // ...
  try {
    templateParser.parseTemplateAsSourceFile(/* ... */);
  } catch (err) {
    // ...
  }
}
// test.ts
import * as templateParser from './parse_template';

it('test', () => {
  spyOn(templateParser, 'parseTemplateAsSourceFile').and.returnValue(/* ... */);

  // ...
});

parseTemplateAsSourceFileForTest = null;
}

function parseTemplateAsSourceFile(fileName: string, template: string): ts.SourceFile {
Copy link
Contributor

@dgp1130 dgp1130 Nov 24, 2021

Choose a reason for hiding this comment

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

Spying on an internal function is a bit awkward, but set*() and reset*() are already exported to work around this, so I'm not sure it's that much worse.

If ts.createSourceFile() is too noisy, then we could make parseTemplateAsSourceFile() just call through:

export function parseTemplateAsSourceFile(fileName: string, template: string): ts.SourceFile {
  return ts.createSourceFile(fileName, template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
}

We could put this in another file on its own so as not to pollute the public API of diagnostic.ts and then it can be spied on in the test.

I haven't tried Jasmine spies with real ES modules, so I'm not sure if there are caveats to it. You may need to import * as foo from '...'; so the spy will pick up the call. I still think that's an improvement over putting multiple test-only functions into the prod build and inventing our own mocking semantics for this file.

// diagnostic.ts
import * as templateParser from './parse_template';

export function makeTemplateDiagnostic(/* ... */): TemplateDiagnostic {
  // ...
  try {
    templateParser.parseTemplateAsSourceFile(/* ... */);
  } catch (err) {
    // ...
  }
}
// test.ts
import * as templateParser from './parse_template';

it('test', () => {
  spyOn(templateParser, 'parseTemplateAsSourceFile').and.returnValue(/* ... */);

  // ...
});

JoostK added 2 commits Nov 24, 2021
…ate error fails

When the Angular compiler emits a diagnostic in a template file, it
forces TypeScript to parse that template. Templates are not TypeScript,
so this parse finds a bunch of parsing errors, which Angular then
ignores and we show the diagnostic anyways because we have more context.
This can lead to strange behavior in TypeScript because templates are so
weird that it can break the parser and crash the whole compiler.

For example, certain Angular templates can encounter failures fixed by
microsoft/TypeScript#45987, which are not easily debuggable and require
a TS upgrade to fix.

This commit introduces logic to handle the error gracefully, by falling
back to report the template error on the component class itself. The
diagnostic is extended to still reference the template location and
includes the failure's stack trace, to allow the parsing failure to be
reported to TypeScript (as parsing should in theory not cause a crash).

Closes angular#43970
@JoostK JoostK force-pushed the ngtsc/degraded-template-diags branch from 6c42323 to 2a188dc Nov 24, 2021
@JoostK JoostK force-pushed the ngtsc/degraded-template-diags branch from 2a188dc to 93f6d88 Nov 24, 2021
@JoostK JoostK requested a review from dgp1130 Nov 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

2 participants
X Tutup