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
Unknown pipe & local ref diagnostics #33454
Conversation
71391fd
to
fd267e8
Compare
import {TcbSourceResolver, absoluteSourceSpanToSourceLocation, makeTemplateDiagnostic} from './diagnostics'; | ||
|
||
/** | ||
* Collects reports on template diagnostics which are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completed comment.
const mapping = this.resolver.getSourceMapping(id); | ||
const value = ref.value.trim(); | ||
|
||
const errorMsg = `No directive found with exportAs '${value}'.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future consideration: have an intuitive way of visualising the module scope/available directive exports to better indicate what needs to happen to resolve this diagnostic. Because it's a non-local issue, how to fix it is not straightforward (and one of the areas I would like to see Angular become better is in error reporting!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely agree. One of the goals of having the centralized ErrorCode
for Angular errors was to keep track of the different classes of errors and make it easier for us to set goals of improving them.
@@ -797,6 +798,7 @@ class Scope { | |||
for (const child of node.children) { | |||
this.appendNode(child); | |||
} | |||
this.checkReferencesOfNode(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am surprised you took this approach, intertwining the reporting of missing refs with the generation of TCBs, instead of adding some diagnostic reporting facility to R3TargetBinder
itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered this, actually. Part of the problem is that the way we map expressions back into the original template source depends a lot on the way we traverse the template. This doesn't matter for Reference
s which are a top level concept and have proper source spans, but for BindingPipe
s which are an AST
node, the source mapping depends on the context and the R3TargetBinder
doesn't currently deal with those concerns.
Additionally, it's easier within the TCB generator to conform to the checkTemplateBodies
config option.
Ultimately we might refactor in this direction. Especially because the HTML-level template parsing errors are not currently surfaced via our "nice" diagnostics mechanism. We'll eventually want to port that over too.
Maybe it would make more sense to refactor the template traversal out of the TCB code, and make TCB generation a side effect as well. So the core template typechecking engine would walk through the template, and call into each "plugin" to do things like check reference validity, check the DOM schema, and cause TCBs to be emitted into the type check file.
@@ -1025,7 +1036,11 @@ class TcbExpressionTranslator { | |||
} else if (binding instanceof TmplAstReference) { | |||
const target = this.tcb.boundTarget.getReferenceTarget(binding); | |||
if (target === null) { | |||
throw new Error(`Unbound reference? ${binding.name}`); | |||
// This reference is unbound. | |||
this.tcb.oobRecorder.missingReferenceTarget(this.tcb.id, binding); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am under the impression that reporting this both here and in checkReferencesOfNode
is not ideal and may introduce the same diagnostic twice.
For instance:
<div dir #ref="nonexistent">{{ ref.value }}</div>
This will create two diagnostics with identical message but different spans, if I'm not mistaken:
<div dir #ref="nonexistent">{{ ref.value }}</div>
^~~~~~~~~~~~~~~~~~ ^~~
Both have the message No directive found with exportAs 'nonexistent'.
. For the first diagnostics this message makes sense, for the second it does not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good point! Fixed.
if (dirTarget === null) { | ||
// TODO(alxhub): Return an error value here that can be used for template validation. | ||
throw new Error(`Assertion error: failed to find directive with exportAs: ${ref.value}`); | ||
// No matching directive was found - this reference points to an unknown target. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a comment on L263 that mentions "One should exist.", this is now outdated.
Also, this is the place where I expected some error reporting mechanism to be used, instead of in the TCB generation code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
@@ -55,6 +55,11 @@ export function toAbsoluteSpan(span: ParseSpan, sourceSpan: ParseSourceSpan): Ab | |||
return <AbsoluteSpan>{start: span.start + offset, end: span.end + offset}; | |||
} | |||
|
|||
export function absoluteSourceSpanToSourceLocation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe we should just decompose TcbSourceResolver.sourceLocationToSpan
to receive two arguments instead of the SourceLocation
bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all for refactoring this to clean it up :)
const location = absoluteSourceSpanToSourceLocation(id, absSpan); | ||
const sourceSpan = this.resolver.sourceLocationToSpan(location); | ||
if (sourceSpan === null) { | ||
throw new Error(`Could not map location for error`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty cryptic error message ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated :)
@@ -20,12 +20,16 @@ export interface OutOfBandDiagnosticRecorder { | |||
readonly diagnostics: ts.Diagnostic[]; | |||
|
|||
missingReferenceTarget(id: string, ref: TmplAstReference): void; | |||
|
|||
missingPipe(id: string, ast: BindingPipe, sourceSpan: AbsoluteSourceSpan): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now realize that id
corresponds with the TCB id, not some pipe id which the function name suggests. These methods could use docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to templateId
, and added docs.
@@ -975,9 +977,14 @@ class TcbExpressionTranslator { | |||
return ts.createIdentifier('ctx'); | |||
} else if (ast instanceof BindingPipe) { | |||
const expr = this.translate(ast.exp); | |||
let pipe: ts.Expression; | |||
let pipe: ts.Expression|null; | |||
const nameAbsoluteSpan = toAbsoluteSpan(ast.nameSpan, this.sourceSpan); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move nameAbsoluteSpan
into the if (pipe === null)
below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -34,9 +35,10 @@ import {checkIfClassIsExported, checkIfGenericTypesAreUnbound, tsCallMethod, tsC | |||
*/ | |||
export function generateTypeCheckBlock( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should make clear that this function has a side-effect when it comes to producing diagnostics. Having the out of bands parameter does indicate this somewhat, but it could be made more explicit. Come to think of it, it would be a good idea for future reference to document OutOfBandDiagnosticRecorder
on why it exists and how it's used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I updated and expanded the comment to talk about the verifications which happen during generation.
As mentioned above, I think the "right way" to do this is to refactor the template traversal to be independent of everything, and hang various operations off of this central traversal, including both the verification diagnostics and TCB generation.
c72ec40
to
65f8c43
Compare
Presubmit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some more nits, otherwise LGTM
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
Previously the template binder would crash when encountering an unknown localref (# reference) such as `<div #ref="foo">` when no directive has `exportAs: "foo"`. With this commit, the compiler instead generates a template diagnostic error informing the user about the invalid reference.
Previously the compiler would crash if a pipe was encountered which did not match any pipe in the scope of a template. This commit introduces a new diagnostic error for unknown pipes instead.
65f8c43
to
8ee5c22
Compare
Previously the template binder would crash when encountering an unknown localref (# reference) such as `<div #ref="foo">` when no directive has `exportAs: "foo"`. With this commit, the compiler instead generates a template diagnostic error informing the user about the invalid reference. PR Close #33454
Previously the compiler would crash if a pipe was encountered which did not match any pipe in the scope of a template. This commit introduces a new diagnostic error for unknown pipes instead. PR Close #33454
Previously the compiler would crash if a pipe was encountered which did not match any pipe in the scope of a template. This commit introduces a new diagnostic error for unknown pipes instead. PR Close #33454
When do you plan to release it? |
Previously the template binder would crash when encountering an unknown localref (# reference) such as `<div #ref="foo">` when no directive has `exportAs: "foo"`. With this commit, the compiler instead generates a template diagnostic error informing the user about the invalid reference. PR Close angular#33454
Previously the compiler would crash if a pipe was encountered which did not match any pipe in the scope of a template. This commit introduces a new diagnostic error for unknown pipes instead. PR Close angular#33454
Previously the template binder would crash when encountering an unknown localref (# reference) such as `<div #ref="foo">` when no directive has `exportAs: "foo"`. With this commit, the compiler instead generates a template diagnostic error informing the user about the invalid reference. PR Close angular#33454
Previously the compiler would crash if a pipe was encountered which did not match any pipe in the scope of a template. This commit introduces a new diagnostic error for unknown pipes instead. PR Close angular#33454
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
No description provided.