From b40f1e5b86bb9fb7413be504e9c6a6f80f60460f Mon Sep 17 00:00:00 2001 From: Dylan Hunn Date: Mon, 4 Mar 2024 12:17:24 -0800 Subject: [PATCH 1/7] refactor(compiler): Remove deep imports in the language service (#54695) Previously, the language service relied on deep imports such as `@angular/compiler/render3/...`. This is bad form, because that creates a dependency on the package's internal structure. Additionally, this is not compatible with google3. In this PR, I replace all the deep imports with shallow imports, in some cases adding the missing symbol to the `compiler.ts` exports. PR Close #54695 --- packages/compiler/src/compiler.ts | 2 +- .../codefixes/fix_invalid_banana_in_box.ts | 9 +- .../src/codefixes/fix_missing_import.ts | 5 +- .../src/codefixes/fix_missing_member.ts | 2 - packages/language-service/src/completions.ts | 3 +- .../language-service/src/outlining_spans.ts | 21 +- packages/language-service/src/quick_info.ts | 5 +- .../src/quick_info_built_ins.ts | 14 +- .../language-service/src/template_target.ts | 200 +++++++++--------- packages/language-service/src/utils.ts | 63 +++--- 10 files changed, 161 insertions(+), 163 deletions(-) diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 9c07d7228eb62..7755f16b42c93 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -63,7 +63,7 @@ export {SourceMap} from './output/source_map'; export * from './injectable_compiler_2'; export * from './render3/partial/api'; export * from './render3/view/api'; -export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, Element as TmplAstElement, Icu as TmplAstIcu, Node as TmplAstNode, RecursiveVisitor as TmplAstRecursiveVisitor, Reference as TmplAstReference, Template as TmplAstTemplate, Text as TmplAstText, TextAttribute as TmplAstTextAttribute, Variable as TmplAstVariable, DeferredBlock as TmplAstDeferredBlock, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockError as TmplAstDeferredBlockError, DeferredTrigger as TmplAstDeferredTrigger, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, DeferredBlockTriggers as TmplAstDeferredBlockTriggers, UnknownBlock as TmplAstUnknownBlock} from './render3/r3_ast'; +export {visitAll as tmplAstVisitAll, BlockNode as TmplAstBlockNode, BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, Element as TmplAstElement, Icu as TmplAstIcu, Node as TmplAstNode, Visitor as TmplAstVisitor, RecursiveVisitor as TmplAstRecursiveVisitor, Reference as TmplAstReference, Template as TmplAstTemplate, Text as TmplAstText, TextAttribute as TmplAstTextAttribute, Variable as TmplAstVariable, DeferredBlock as TmplAstDeferredBlock, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockError as TmplAstDeferredBlockError, DeferredTrigger as TmplAstDeferredTrigger, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, DeferredBlockTriggers as TmplAstDeferredBlockTriggers, UnknownBlock as TmplAstUnknownBlock} from './render3/r3_ast'; export * from './render3/view/t2_api'; export * from './render3/view/t2_binder'; export {createCssSelectorFromNode} from './render3/view/util'; diff --git a/packages/language-service/src/codefixes/fix_invalid_banana_in_box.ts b/packages/language-service/src/codefixes/fix_invalid_banana_in_box.ts index 080e3445f7ec7..90aa76d953db2 100644 --- a/packages/language-service/src/codefixes/fix_invalid_banana_in_box.ts +++ b/packages/language-service/src/codefixes/fix_invalid_banana_in_box.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import {TmplAstBoundEvent} from '@angular/compiler'; import {ErrorCode, ngErrorCode} from '@angular/compiler-cli/src/ngtsc/diagnostics'; -import {BoundEvent} from '@angular/compiler/src/render3/r3_ast'; import tss from 'typescript/lib/tsserverlibrary'; import {getTargetAtPosition, TargetNodeKind} from '../template_target'; @@ -85,7 +85,8 @@ export const fixInvalidBananaInBoxMeta: CodeActionMeta = { }, }; -function getTheBoundEventAtPosition(templateInfo: TemplateInfo, start: number): BoundEvent|null { +function getTheBoundEventAtPosition(templateInfo: TemplateInfo, start: number): TmplAstBoundEvent| + null { // It's safe to get the bound event at the position `start + 1` because the `start` is at the // start of the diagnostic, and the node outside the attribute key and value spans are skipped by // the function `getTargetAtPosition`. @@ -97,7 +98,7 @@ function getTheBoundEventAtPosition(templateInfo: TemplateInfo, start: number): } if (positionDetail.context.kind !== TargetNodeKind.AttributeInKeyContext || - !(positionDetail.context.node instanceof BoundEvent)) { + !(positionDetail.context.node instanceof TmplAstBoundEvent)) { return null; } @@ -107,7 +108,7 @@ function getTheBoundEventAtPosition(templateInfo: TemplateInfo, start: number): /** * Flip the invalid "box in a banana" `([thing])` to the correct "banana in a box" `[(thing)]`. */ -function convertBoundEventToTsTextChange(node: BoundEvent): readonly tss.TextChange[] { +function convertBoundEventToTsTextChange(node: TmplAstBoundEvent): readonly tss.TextChange[] { const name = node.name; const boundSyntax = node.sourceSpan.toString(); const expectedBoundSyntax = boundSyntax.replace(`(${name})`, `[(${name.slice(1, -1)})]`); diff --git a/packages/language-service/src/codefixes/fix_missing_import.ts b/packages/language-service/src/codefixes/fix_missing_import.ts index ca92f09cd6310..3de6a5f6e098d 100644 --- a/packages/language-service/src/codefixes/fix_missing_import.ts +++ b/packages/language-service/src/codefixes/fix_missing_import.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ASTWithName} from '@angular/compiler'; +import {ASTWithName, TmplAstElement} from '@angular/compiler'; import {ErrorCode as NgCompilerErrorCode, ngErrorCode} from '@angular/compiler-cli/src/ngtsc/diagnostics/index'; import {PotentialDirective, PotentialImportMode, PotentialPipe} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST import ts from 'typescript'; import {getTargetAtPosition, TargetNodeKind} from '../template_target'; @@ -52,7 +51,7 @@ function getCodeActions( let matches: Set|Set; if (target.context.kind === TargetNodeKind.ElementInTagContext && - target.context.node instanceof t.Element) { + target.context.node instanceof TmplAstElement) { const allPossibleDirectives = checker.getPotentialTemplateDirectives(templateInfo.component); matches = getDirectiveMatchesForElementTag(target.context.node, allPossibleDirectives); } else if ( diff --git a/packages/language-service/src/codefixes/fix_missing_member.ts b/packages/language-service/src/codefixes/fix_missing_member.ts index 53e779719c987..13dfc8521ee30 100644 --- a/packages/language-service/src/codefixes/fix_missing_member.ts +++ b/packages/language-service/src/codefixes/fix_missing_member.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {findFirstMatchingNode} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments'; -import * as e from '@angular/compiler/src/expression_parser/ast'; // e for expression AST import ts from 'typescript'; import tss from 'typescript/lib/tsserverlibrary'; diff --git a/packages/language-service/src/completions.ts b/packages/language-service/src/completions.ts index 0253620ed1ec3..0bf43153aa718 100644 --- a/packages/language-service/src/completions.ts +++ b/packages/language-service/src/completions.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {AST, ASTWithSource, BindingPipe, BindingType, Call, EmptyExpr, ImplicitReceiver, LiteralPrimitive, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler'; +import {AST, ASTWithSource, BindingPipe, BindingType, Call, EmptyExpr, ImplicitReceiver, LiteralPrimitive, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundEvent as BoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstSwitchBlock as SwitchBlock, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstTextAttribute as TextAttribute, TmplAstUnknownBlock as UnknownBlock, TmplAstVariable} from '@angular/compiler'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {CompletionKind, PotentialDirective, SymbolKind, TemplateDeclarationSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import {BoundEvent, DeferredBlock, SwitchBlock, TextAttribute, UnknownBlock} from '@angular/compiler/src/render3/r3_ast'; import ts from 'typescript'; import {addAttributeCompletionEntries, AsciiSortPriority, AttributeCompletionKind, buildAnimationCompletionEntries, buildAttributeCompletionTable, getAttributeCompletionSymbol} from './attribute_completions'; diff --git a/packages/language-service/src/outlining_spans.ts b/packages/language-service/src/outlining_spans.ts index b1bfe6362c44e..d72277f583524 100644 --- a/packages/language-service/src/outlining_spans.ts +++ b/packages/language-service/src/outlining_spans.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ParseLocation, ParseSourceSpan} from '@angular/compiler'; +import {ParseLocation, ParseSourceSpan, TmplAstBlockNode, TmplAstDeferredBlock, TmplAstForLoopBlock, TmplAstIfBlock, TmplAstNode, TmplAstRecursiveVisitor, tmplAstVisitAll} from '@angular/compiler'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {isExternalResource} from '@angular/compiler-cli/src/ngtsc/metadata'; import {isNamedClassDeclaration} from '@angular/compiler-cli/src/ngtsc/reflection'; -import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST import ts from 'typescript'; import {getFirstComponentForTemplateFile, isTypeScriptFile, toTextSpan} from './utils'; @@ -22,7 +21,7 @@ export function getOutliningSpans(compiler: NgCompiler, fileName: string): ts.Ou return []; } - const templatesInFile: Array = []; + const templatesInFile: Array = []; for (const stmt of sf.statements) { if (isNamedClassDeclaration(stmt)) { const resources = compiler.getComponentResources(stmt); @@ -47,19 +46,19 @@ export function getOutliningSpans(compiler: NgCompiler, fileName: string): ts.Ou } } -class BlockVisitor extends t.RecursiveVisitor { - readonly blocks = [] as Array; +class BlockVisitor extends TmplAstRecursiveVisitor { + readonly blocks = [] as Array; - static getBlockSpans(templateNodes: t.Node[]): ts.OutliningSpan[] { + static getBlockSpans(templateNodes: TmplAstNode[]): ts.OutliningSpan[] { const visitor = new BlockVisitor(); - t.visitAll(visitor, templateNodes); + tmplAstVisitAll(visitor, templateNodes); const {blocks} = visitor; return blocks.map(block => { let mainBlockSpan = block.sourceSpan; // The source span of for loops and deferred blocks contain all parts (ForLoopBlockEmpty, // DeferredBlockLoading, etc.). The folding range should only include the main block span for // these. - if (block instanceof t.ForLoopBlock || block instanceof t.DeferredBlock) { + if (block instanceof TmplAstForLoopBlock || block instanceof TmplAstDeferredBlock) { mainBlockSpan = block.mainBlockSpan; } return { @@ -75,10 +74,10 @@ class BlockVisitor extends t.RecursiveVisitor { }); } - visit(node: t.Node) { - if (node instanceof t.BlockNode + visit(node: TmplAstNode) { + if (node instanceof TmplAstBlockNode // Omit `IfBlock` because we include the branches individually - && !(node instanceof t.IfBlock)) { + && !(node instanceof TmplAstIfBlock)) { this.blocks.push(node); } } diff --git a/packages/language-service/src/quick_info.ts b/packages/language-service/src/quick_info.ts index 8fd498c399ae1..458a4da65ea90 100644 --- a/packages/language-service/src/quick_info.ts +++ b/packages/language-service/src/quick_info.ts @@ -5,10 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AST, TmplAstBoundAttribute, TmplAstNode, TmplAstTextAttribute} from '@angular/compiler'; +import {AST, TmplAstBlockNode, TmplAstBoundAttribute, TmplAstDeferredTrigger, TmplAstNode, TmplAstTextAttribute} from '@angular/compiler'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, InputBindingSymbol, OutputBindingSymbol, PipeSymbol, ReferenceSymbol, Symbol, SymbolKind, TcbLocation, VariableSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import {BlockNode, DeferredTrigger} from '@angular/compiler/src/render3/r3_ast'; import ts from 'typescript'; import {DisplayInfoKind, SYMBOL_PUNC, SYMBOL_SPACE, SYMBOL_TEXT} from './display_parts'; @@ -26,7 +25,7 @@ export class QuickInfoBuilder { private readonly positionDetails: TemplateTarget) {} get(): ts.QuickInfo|undefined { - if (this.node instanceof DeferredTrigger || this.node instanceof BlockNode) { + if (this.node instanceof TmplAstDeferredTrigger || this.node instanceof TmplAstBlockNode) { return createQuickInfoForBuiltIn(this.node, this.positionDetails.position); } diff --git a/packages/language-service/src/quick_info_built_ins.ts b/packages/language-service/src/quick_info_built_ins.ts index 4179dee9a92a1..b695a35902e7f 100644 --- a/packages/language-service/src/quick_info_built_ins.ts +++ b/packages/language-service/src/quick_info_built_ins.ts @@ -5,8 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AST, Call, ImplicitReceiver, ParseSourceSpan, PropertyRead, ThisReceiver, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstNode} from '@angular/compiler'; -import {BlockNode, DeferredTrigger, ForLoopBlock, ForLoopBlockEmpty} from '@angular/compiler/src/render3/r3_ast'; +import {AST, Call, ImplicitReceiver, ParseSourceSpan, PropertyRead, ThisReceiver, TmplAstBlockNode, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstNode} from '@angular/compiler'; import ts from 'typescript'; import {DisplayInfoKind, SYMBOL_TEXT} from './display_parts'; @@ -50,9 +49,10 @@ export function createNgTemplateQuickInfo(node: TmplAstNode|AST): ts.QuickInfo { } export function createQuickInfoForBuiltIn( - node: DeferredTrigger|BlockNode, cursorPositionInTemplate: number): ts.QuickInfo|undefined { + node: TmplAstDeferredTrigger|TmplAstBlockNode, cursorPositionInTemplate: number): ts.QuickInfo| + undefined { let partSpan: ParseSourceSpan; - if (node instanceof DeferredTrigger) { + if (node instanceof TmplAstDeferredTrigger) { if (node.prefetchSpan !== null && isWithin(cursorPositionInTemplate, node.prefetchSpan)) { partSpan = node.prefetchSpan; } else if ( @@ -68,10 +68,12 @@ export function createQuickInfoForBuiltIn( if (node instanceof TmplAstDeferredBlock || node instanceof TmplAstDeferredBlockError || node instanceof TmplAstDeferredBlockLoading || node instanceof TmplAstDeferredBlockPlaceholder || - node instanceof ForLoopBlockEmpty && isWithin(cursorPositionInTemplate, node.nameSpan)) { + node instanceof TmplAstForLoopBlockEmpty && + isWithin(cursorPositionInTemplate, node.nameSpan)) { partSpan = node.nameSpan; } else if ( - node instanceof ForLoopBlock && isWithin(cursorPositionInTemplate, node.trackKeywordSpan)) { + node instanceof TmplAstForLoopBlock && + isWithin(cursorPositionInTemplate, node.trackKeywordSpan)) { partSpan = node.trackKeywordSpan; } else { return undefined; diff --git a/packages/language-service/src/template_target.ts b/packages/language-service/src/template_target.ts index 4a5eafd1bbcd9..46031011f8134 100644 --- a/packages/language-service/src/template_target.ts +++ b/packages/language-service/src/template_target.ts @@ -6,11 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ParseSourceSpan, ParseSpan, TmplAstBoundEvent} from '@angular/compiler'; +import {AbsoluteSourceSpan, AST, ASTWithSource, Call, ImplicitReceiver, ParseSourceSpan, ParseSpan, PropertyRead, RecursiveAstVisitor, SafeCall, TmplAstBoundAttribute, TmplAstBoundDeferredTrigger, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstUnknownBlock, TmplAstVariable, tmplAstVisitAll, TmplAstVisitor} from '@angular/compiler'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {findFirstMatchingNode} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments'; -import * as e from '@angular/compiler/src/expression_parser/ast'; // e for expression AST -import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST import tss from 'typescript/lib/tsserverlibrary'; import {isBoundEventWithSyntheticHandler, isTemplateNodeWithKeyAndValue, isWithin, isWithinKeyValue, TemplateInfo} from './utils'; @@ -30,24 +28,24 @@ export interface TemplateTarget { context: TargetContext; /** - * The `t.Template` which contains the found node or expression (or `null` if in the root + * The `TmplAstTemplate` which contains the found node or expression (or `null` if in the root * template). */ - template: t.Template|null; + template: TmplAstTemplate|null; /** * The immediate parent node of the targeted node. */ - parent: t.Node|e.AST|null; + parent: TmplAstNode|AST|null; } /** * A node or nodes targeted at a given position in the template, including potential contextual * information about the specific aspect of the node being referenced. * - * Some nodes have multiple interior contexts. For example, `t.Element` nodes have both a tag name - * as well as a body, and a given position definitively points to one or the other. `TargetNode` - * captures the node itself, as well as this additional contextual disambiguation. + * Some nodes have multiple interior contexts. For example, `TmplAstElement` nodes have both a tag + * name as well as a body, and a given position definitively points to one or the other. + * `TargetNode` captures the node itself, as well as this additional contextual disambiguation. */ export type TargetContext = SingleNodeTarget|MultiNodeTarget; @@ -78,12 +76,12 @@ export enum TargetNodeKind { } /** - * An `e.AST` expression that's targeted at a given position, with no additional context. + * An `AST` expression that's targeted at a given position, with no additional context. */ export interface RawExpression { kind: TargetNodeKind.RawExpression; - node: e.AST; - parents: e.AST[]; + node: AST; + parents: AST[]; } /** @@ -96,59 +94,59 @@ export interface RawExpression { */ export interface CallExpressionInArgContext { kind: TargetNodeKind.CallExpressionInArgContext; - node: e.Call|e.SafeCall; + node: Call|SafeCall; } /** - * A `t.Node` template node that's targeted at a given position, with no additional context. + * A `TmplAstNode` template node that's targeted at a given position, with no additional context. */ export interface RawTemplateNode { kind: TargetNodeKind.RawTemplateNode; - node: t.Node; + node: TmplAstNode; } /** - * A `t.Element` (or `t.Template`) element node that's targeted, where the given position is within - * the tag name. + * A `TmplAstElement` (or `TmplAstTemplate`) element node that's targeted, where the given position + * is within the tag name. */ export interface ElementInTagContext { kind: TargetNodeKind.ElementInTagContext; - node: t.Element|t.Template; + node: TmplAstElement|TmplAstTemplate; } /** - * A `t.Element` (or `t.Template`) element node that's targeted, where the given position is within - * the element body. + * A `TmplAstElement` (or `TmplAstTemplate`) element node that's targeted, where the given position + * is within the element body. */ export interface ElementInBodyContext { kind: TargetNodeKind.ElementInBodyContext; - node: t.Element|t.Template; + node: TmplAstElement|TmplAstTemplate; } export interface AttributeInKeyContext { kind: TargetNodeKind.AttributeInKeyContext; - node: t.TextAttribute|t.BoundAttribute|t.BoundEvent; + node: TmplAstTextAttribute|TmplAstBoundAttribute|TmplAstBoundEvent; } export interface AttributeInValueContext { kind: TargetNodeKind.AttributeInValueContext; - node: t.TextAttribute|t.BoundAttribute|t.BoundEvent; + node: TmplAstTextAttribute|TmplAstBoundAttribute|TmplAstBoundEvent; } /** - * A `t.BoundAttribute` and `t.BoundEvent` pair that are targeted, where the given position is - * within the key span of both. + * A `TmplAstBoundAttribute` and `TmplAstBoundEvent` pair that are targeted, where the given + * position is within the key span of both. */ export interface TwoWayBindingContext { kind: TargetNodeKind.TwoWayBindingContext; - nodes: [t.BoundAttribute, t.BoundEvent]; + nodes: [TmplAstBoundAttribute, TmplAstBoundEvent]; } /** * Special marker AST that can be used when the cursor is within the `sourceSpan` but not * the key or value span of a node with key/value spans. */ -class OutsideKeyValueMarkerAst extends e.AST { +class OutsideKeyValueMarkerAst extends AST { override visit(): null { return null; } @@ -159,7 +157,7 @@ class OutsideKeyValueMarkerAst extends e.AST { * or value span of a node with key/value spans. */ const OUTSIDE_K_V_MARKER = - new OutsideKeyValueMarkerAst(new ParseSpan(-1, -1), new e.AbsoluteSourceSpan(-1, -1)); + new OutsideKeyValueMarkerAst(new ParseSpan(-1, -1), new AbsoluteSourceSpan(-1, -1)); /** * Return the template AST node or expression AST node that most accurately @@ -168,18 +166,20 @@ const OUTSIDE_K_V_MARKER = * @param template AST tree of the template * @param position target cursor position */ -export function getTargetAtPosition(template: t.Node[], position: number): TemplateTarget|null { +export function getTargetAtPosition(template: TmplAstNode[], position: number): TemplateTarget| + null { const path = TemplateTargetVisitor.visitTemplate(template, position); if (path.length === 0) { return null; } const candidate = path[path.length - 1]; - // Walk up the result nodes to find the nearest `t.Template` which contains the targeted node. - let context: t.Template|null = null; + // Walk up the result nodes to find the nearest `TmplAstTemplate` which contains the targeted + // node. + let context: TmplAstTemplate|null = null; for (let i = path.length - 2; i >= 0; i--) { const node = path[i]; - if (node instanceof t.Template) { + if (node instanceof TmplAstTemplate) { context = node; break; } @@ -187,14 +187,14 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ // Given the candidate node, determine the full targeted context. let nodeInContext: TargetContext; - if ((candidate instanceof e.Call || candidate instanceof e.SafeCall) && + if ((candidate instanceof Call || candidate instanceof SafeCall) && isWithin(position, candidate.argumentSpan)) { nodeInContext = { kind: TargetNodeKind.CallExpressionInArgContext, node: candidate, }; - } else if (candidate instanceof e.AST) { - const parents = path.filter((value: e.AST|t.Node): value is e.AST => value instanceof e.AST); + } else if (candidate instanceof AST) { + const parents = path.filter((value: AST|TmplAstNode): value is AST => value instanceof AST); // Remove the current node from the parents list. parents.pop(); @@ -203,7 +203,7 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ node: candidate, parents, }; - } else if (candidate instanceof t.Element) { + } else if (candidate instanceof TmplAstElement) { // Elements have two contexts: the tag context (position is within the element tag) or the // element body context (position is outside of the tag name, but still in the element). @@ -223,14 +223,15 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ }; } } else if ( - (candidate instanceof t.BoundAttribute || candidate instanceof t.BoundEvent || - candidate instanceof t.TextAttribute) && + (candidate instanceof TmplAstBoundAttribute || candidate instanceof TmplAstBoundEvent || + candidate instanceof TmplAstTextAttribute) && candidate.keySpan !== undefined) { const previousCandidate = path[path.length - 2]; - if (candidate instanceof t.BoundEvent && previousCandidate instanceof t.BoundAttribute && + if (candidate instanceof TmplAstBoundEvent && + previousCandidate instanceof TmplAstBoundAttribute && candidate.name === previousCandidate.name + 'Change') { - const boundAttribute: t.BoundAttribute = previousCandidate; - const boundEvent: t.BoundEvent = candidate; + const boundAttribute: TmplAstBoundAttribute = previousCandidate; + const boundEvent: TmplAstBoundEvent = candidate; nodeInContext = { kind: TargetNodeKind.TwoWayBindingContext, nodes: [boundAttribute, boundEvent], @@ -253,7 +254,7 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ }; } - let parent: t.Node|e.AST|null = null; + let parent: TmplAstNode|AST|null = null; if (nodeInContext.kind === TargetNodeKind.TwoWayBindingContext && path.length >= 3) { parent = path[path.length - 3]; } else if (path.length >= 2) { @@ -264,7 +265,7 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ } function findFirstMatchingNodeForSourceSpan( - tcb: tss.Node, sourceSpan: ParseSourceSpan|e.AbsoluteSourceSpan) { + tcb: tss.Node, sourceSpan: ParseSourceSpan|AbsoluteSourceSpan) { return findFirstMatchingNode( tcb, { @@ -302,7 +303,7 @@ export function getTcbNodesOfTemplateAtPosition( const tcbNodes: (tss.Node|null)[] = []; if (target.context.kind === TargetNodeKind.RawExpression) { const targetNode = target.context.node; - if (targetNode instanceof e.PropertyRead) { + if (targetNode instanceof PropertyRead) { const tsNode = findFirstMatchingNode(tcb, { withSpan: targetNode.nameSpan, filter: (node): node is tss.PropertyAccessExpression => tss.isPropertyAccessExpression(node) @@ -331,12 +332,12 @@ export function getTcbNodesOfTemplateAtPosition( * position, as well as records the path of increasingly nested nodes that were traversed to reach * that position. */ -class TemplateTargetVisitor implements t.Visitor { +class TemplateTargetVisitor implements TmplAstVisitor { // We need to keep a path instead of the last node because we might need more // context for the last node, for example what is the parent node? - readonly path: Array = []; + readonly path: Array = []; - static visitTemplate(template: t.Node[], position: number): Array { + static visitTemplate(template: TmplAstNode[], position: number): Array { const visitor = new TemplateTargetVisitor(position); visitor.visitAll(template); const {path} = visitor; @@ -345,13 +346,13 @@ class TemplateTargetVisitor implements t.Visitor { const candidate = strictPath[strictPath.length - 1]; const matchedASourceSpanButNotAKvSpan = path.some(v => v === OUTSIDE_K_V_MARKER); if (matchedASourceSpanButNotAKvSpan && - (candidate instanceof t.Template || candidate instanceof t.Element)) { - // Template nodes with key and value spans are always defined on a `t.Template` or - // `t.Element`. If we found a node on a template with a `sourceSpan` that includes the cursor, - // it is possible that we are outside the k/v spans (i.e. in-between them). If this is the - // case and we do not have any other candidate matches on the `t.Element` or `t.Template`, we - // want to return no results. Otherwise, the `t.Element`/`t.Template` result is incorrect for - // that cursor position. + (candidate instanceof TmplAstTemplate || candidate instanceof TmplAstElement)) { + // Template nodes with key and value spans are always defined on a `TmplAstTemplate` or + // `TmplAstElement`. If we found a node on a template with a `sourceSpan` that includes the + // cursor, it is possible that we are outside the k/v spans (i.e. in-between them). If this is + // the case and we do not have any other candidate matches on the `TmplAstElement` or + // `TmplAstTemplate`, we want to return no results. Otherwise, the + // `TmplAstElement`/`TmplAstTemplate` result is incorrect for that cursor position. return []; } return strictPath; @@ -360,13 +361,13 @@ class TemplateTargetVisitor implements t.Visitor { // Position must be absolute in the source file. private constructor(private readonly position: number) {} - visit(node: t.Node) { + visit(node: TmplAstNode) { const {start, end} = getSpanIncludingEndTag(node); if (end !== null && !isWithin(this.position, {start, end})) { return; } - const last: t.Node|e.AST|undefined = this.path[this.path.length - 1]; + const last: TmplAstNode|AST|undefined = this.path[this.path.length - 1]; const withinKeySpanOfLastNode = last && isTemplateNodeWithKeyAndValue(last) && isWithin(this.position, last.keySpan); const withinKeySpanOfCurrentNode = @@ -380,7 +381,7 @@ class TemplateTargetVisitor implements t.Visitor { // nodes. return; } - if (last instanceof t.UnknownBlock && isWithin(this.position, last.nameSpan)) { + if (last instanceof TmplAstUnknownBlock && isWithin(this.position, last.nameSpan)) { // Autocompletions such as `@\nfoo`, where a newline follows a bare `@`, would not work // because the language service visitor sees us inside the subsequent text node. We deal with // this with using a special-case: if we are completing inside the name span, we don't @@ -398,33 +399,34 @@ class TemplateTargetVisitor implements t.Visitor { } } - visitElement(element: t.Element) { + visitElement(element: TmplAstElement) { this.visitElementOrTemplate(element); } - visitTemplate(template: t.Template) { + visitTemplate(template: TmplAstTemplate) { this.visitElementOrTemplate(template); } - visitElementOrTemplate(element: t.Template|t.Element) { + visitElementOrTemplate(element: TmplAstTemplate|TmplAstElement) { this.visitAll(element.attributes); this.visitAll(element.inputs); - // We allow the path to contain both the `t.BoundAttribute` and `t.BoundEvent` for two-way - // bindings but do not want the path to contain both the `t.BoundAttribute` with its - // children when the position is in the value span because we would then logically create a path - // that also contains the `PropertyWrite` from the `t.BoundEvent`. This early return condition - // ensures we target just `t.BoundAttribute` for this case and exclude `t.BoundEvent` children. + // We allow the path to contain both the `TmplAstBoundAttribute` and `TmplAstBoundEvent` for + // two-way bindings but do not want the path to contain both the `TmplAstBoundAttribute` with + // its children when the position is in the value span because we would then logically create a + // path that also contains the `PropertyWrite` from the `TmplAstBoundEvent`. This early return + // condition ensures we target just `TmplAstBoundAttribute` for this case and exclude + // `TmplAstBoundEvent` children. if (this.path[this.path.length - 1] !== element && - !(this.path[this.path.length - 1] instanceof t.BoundAttribute)) { + !(this.path[this.path.length - 1] instanceof TmplAstBoundAttribute)) { return; } this.visitAll(element.outputs); - if (element instanceof t.Template) { + if (element instanceof TmplAstTemplate) { this.visitAll(element.templateAttrs); } this.visitAll(element.references); - if (element instanceof t.Template) { + if (element instanceof TmplAstTemplate) { this.visitAll(element.variables); } @@ -437,43 +439,43 @@ class TemplateTargetVisitor implements t.Visitor { this.visitAll(element.children); } - visitContent(content: t.Content) { - t.visitAll(this, content.attributes); + visitContent(content: TmplAstContent) { + tmplAstVisitAll(this, content.attributes); } - visitVariable(variable: t.Variable) { + visitVariable(variable: TmplAstVariable) { // Variable has no template nodes or expression nodes. } - visitReference(reference: t.Reference) { + visitReference(reference: TmplAstReference) { // Reference has no template nodes or expression nodes. } - visitTextAttribute(attribute: t.TextAttribute) { + visitTextAttribute(attribute: TmplAstTextAttribute) { // Text attribute has no template nodes or expression nodes. } - visitBoundAttribute(attribute: t.BoundAttribute) { + visitBoundAttribute(attribute: TmplAstBoundAttribute) { if (attribute.valueSpan !== undefined) { this.visitBinding(attribute.value); } } - visitBoundEvent(event: t.BoundEvent) { + visitBoundEvent(event: TmplAstBoundEvent) { if (!isBoundEventWithSyntheticHandler(event)) { this.visitBinding(event.handler); } } - visitText(text: t.Text) { + visitText(text: TmplAstText) { // Text has no template nodes or expression nodes. } - visitBoundText(text: t.BoundText) { + visitBoundText(text: TmplAstBoundText) { this.visitBinding(text.value); } - visitIcu(icu: t.Icu) { + visitIcu(icu: TmplAstIcu) { for (const boundText of Object.values(icu.vars)) { this.visit(boundText); } @@ -482,40 +484,40 @@ class TemplateTargetVisitor implements t.Visitor { } } - visitDeferredBlock(deferred: t.DeferredBlock) { + visitDeferredBlock(deferred: TmplAstDeferredBlock) { deferred.visitAll(this); } - visitDeferredBlockPlaceholder(block: t.DeferredBlockPlaceholder) { + visitDeferredBlockPlaceholder(block: TmplAstDeferredBlockPlaceholder) { this.visitAll(block.children); } - visitDeferredBlockError(block: t.DeferredBlockError) { + visitDeferredBlockError(block: TmplAstDeferredBlockError) { this.visitAll(block.children); } - visitDeferredBlockLoading(block: t.DeferredBlockLoading) { + visitDeferredBlockLoading(block: TmplAstDeferredBlockLoading) { this.visitAll(block.children); } - visitDeferredTrigger(trigger: t.DeferredTrigger) { - if (trigger instanceof t.BoundDeferredTrigger) { + visitDeferredTrigger(trigger: TmplAstDeferredTrigger) { + if (trigger instanceof TmplAstBoundDeferredTrigger) { this.visitBinding(trigger.value); } } - visitSwitchBlock(block: t.SwitchBlock) { + visitSwitchBlock(block: TmplAstSwitchBlock) { this.visitBinding(block.expression); this.visitAll(block.cases); this.visitAll(block.unknownBlocks); } - visitSwitchBlockCase(block: t.SwitchBlockCase) { + visitSwitchBlockCase(block: TmplAstSwitchBlockCase) { block.expression && this.visitBinding(block.expression); this.visitAll(block.children); } - visitForLoopBlock(block: t.ForLoopBlock) { + visitForLoopBlock(block: TmplAstForLoopBlock) { this.visit(block.item); this.visitAll(Object.values(block.contextVariables)); this.visitBinding(block.expression); @@ -524,42 +526,42 @@ class TemplateTargetVisitor implements t.Visitor { block.empty && this.visit(block.empty); } - visitForLoopBlockEmpty(block: t.ForLoopBlockEmpty) { + visitForLoopBlockEmpty(block: TmplAstForLoopBlockEmpty) { this.visitAll(block.children); } - visitIfBlock(block: t.IfBlock) { + visitIfBlock(block: TmplAstIfBlock) { this.visitAll(block.branches); } - visitIfBlockBranch(block: t.IfBlockBranch) { + visitIfBlockBranch(block: TmplAstIfBlockBranch) { block.expression && this.visitBinding(block.expression); block.expressionAlias && this.visit(block.expressionAlias); this.visitAll(block.children); } - visitUnknownBlock(block: t.UnknownBlock) {} + visitUnknownBlock(block: TmplAstUnknownBlock) {} - visitAll(nodes: t.Node[]) { + visitAll(nodes: TmplAstNode[]) { for (const node of nodes) { this.visit(node); } } - private visitBinding(expression: e.AST) { + private visitBinding(expression: AST) { const visitor = new ExpressionVisitor(this.position); visitor.visit(expression, this.path); } } -class ExpressionVisitor extends e.RecursiveAstVisitor { +class ExpressionVisitor extends RecursiveAstVisitor { // Position must be absolute in the source file. constructor(private readonly position: number) { super(); } - override visit(node: e.AST, path: Array) { - if (node instanceof e.ASTWithSource) { + override visit(node: AST, path: Array) { + if (node instanceof ASTWithSource) { // In order to reduce noise, do not include `ASTWithSource` in the path. // For the purpose of source spans, there is no difference between // `ASTWithSource` and underlying node that it wraps. @@ -567,14 +569,14 @@ class ExpressionVisitor extends e.RecursiveAstVisitor { } // The third condition is to account for the implicit receiver, which should // not be visited. - if (isWithin(this.position, node.sourceSpan) && !(node instanceof e.ImplicitReceiver)) { + if (isWithin(this.position, node.sourceSpan) && !(node instanceof ImplicitReceiver)) { path.push(node); node.visit(this, path); } } } -function getSpanIncludingEndTag(ast: t.Node) { +function getSpanIncludingEndTag(ast: TmplAstNode) { const result = { start: ast.sourceSpan.start.offset, end: ast.sourceSpan.end.offset, @@ -584,7 +586,7 @@ function getSpanIncludingEndTag(ast: t.Node) { // the end of the closing tag. Otherwise, for situation like // where the cursor is in the closing tag // we will not be able to return any information. - if (ast instanceof t.Element || ast instanceof t.Template) { + if (ast instanceof TmplAstElement || ast instanceof TmplAstTemplate) { if (ast.endSourceSpan) { result.end = ast.endSourceSpan.end.offset; } else if (ast.children.length > 0) { diff --git a/packages/language-service/src/utils.ts b/packages/language-service/src/utils.ts index edd809fe5e0fd..c1a9dfcfaae5c 100644 --- a/packages/language-service/src/utils.ts +++ b/packages/language-service/src/utils.ts @@ -5,25 +5,23 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AbsoluteSourceSpan, CssSelector, ParseSourceSpan, SelectorMatcher, TmplAstBoundEvent} from '@angular/compiler'; +import {AbsoluteSourceSpan, AST, ASTWithSource, BindingPipe, CssSelector, ImplicitReceiver, LiteralPrimitive, ParseSourceSpan, ParseSpan, PropertyRead, PropertyWrite, SelectorMatcher, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute} from '@angular/compiler'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; import {isExternalResource} from '@angular/compiler-cli/src/ngtsc/metadata'; import {DeclarationNode} from '@angular/compiler-cli/src/ngtsc/reflection'; import {DirectiveSymbol, TemplateTypeChecker} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import * as e from '@angular/compiler/src/expression_parser/ast'; // e for expression AST -import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST import ts from 'typescript'; import {ALIAS_NAME, createDisplayParts, DisplayInfoKind, SYMBOL_PUNC, unsafeCastDisplayInfoKindToScriptElementKind} from './display_parts'; import {findTightestNode, getParentClassDeclaration} from './ts_utils'; -export function getTextSpanOfNode(node: t.Node|e.AST): ts.TextSpan { +export function getTextSpanOfNode(node: TmplAstNode|AST): ts.TextSpan { if (isTemplateNodeWithKeyAndValue(node)) { return toTextSpan(node.keySpan); } else if ( - node instanceof e.PropertyWrite || node instanceof e.BindingPipe || - node instanceof e.PropertyRead) { + node instanceof PropertyWrite || node instanceof BindingPipe || + node instanceof PropertyRead) { // The `name` part of a `PropertyWrite` and `BindingPipe` does not have its own AST // so there is no way to retrieve a `Symbol` for just the `name` via a specific node. return toTextSpan(node.nameSpan); @@ -32,9 +30,9 @@ export function getTextSpanOfNode(node: t.Node|e.AST): ts.TextSpan { } } -export function toTextSpan(span: AbsoluteSourceSpan|ParseSourceSpan|e.ParseSpan): ts.TextSpan { +export function toTextSpan(span: AbsoluteSourceSpan|ParseSourceSpan|ParseSpan): ts.TextSpan { let start: number, end: number; - if (span instanceof AbsoluteSourceSpan || span instanceof e.ParseSpan) { + if (span instanceof AbsoluteSourceSpan || span instanceof ParseSpan) { start = span.start; end = span.end; } else { @@ -44,12 +42,12 @@ export function toTextSpan(span: AbsoluteSourceSpan|ParseSourceSpan|e.ParseSpan) return {start, length: end - start}; } -interface NodeWithKeyAndValue extends t.Node { +interface NodeWithKeyAndValue extends TmplAstNode { keySpan: ParseSourceSpan; valueSpan?: ParseSourceSpan; } -export function isTemplateNodeWithKeyAndValue(node: t.Node|e.AST): node is NodeWithKeyAndValue { +export function isTemplateNodeWithKeyAndValue(node: TmplAstNode|AST): node is NodeWithKeyAndValue { return isTemplateNode(node) && node.hasOwnProperty('keySpan'); } @@ -73,17 +71,17 @@ export function isWithinKeyValue(position: number, node: NodeWithKeyAndValue): b return isWithinKeyValue; } -export function isTemplateNode(node: t.Node|e.AST): node is t.Node { +export function isTemplateNode(node: TmplAstNode|AST): node is TmplAstNode { // Template node implements the Node interface so we cannot use instanceof. return node.sourceSpan instanceof ParseSourceSpan; } -export function isExpressionNode(node: t.Node|e.AST): node is e.AST { - return node instanceof e.AST; +export function isExpressionNode(node: TmplAstNode|AST): node is AST { + return node instanceof AST; } export interface TemplateInfo { - template: t.Node[]; + template: TmplAstNode[]; component: ts.ClassDeclaration; } @@ -169,9 +167,10 @@ export function getFirstComponentForTemplateFile( /** * Given an attribute node, converts it to string form for use as a CSS selector. */ -function toAttributeCssSelector(attribute: t.TextAttribute|t.BoundAttribute|t.BoundEvent): string { +function toAttributeCssSelector(attribute: TmplAstTextAttribute|TmplAstBoundAttribute| + TmplAstBoundEvent): string { let selector: string; - if (attribute instanceof t.BoundEvent || attribute instanceof t.BoundAttribute) { + if (attribute instanceof TmplAstBoundEvent || attribute instanceof TmplAstBoundAttribute) { selector = `[${attribute.name}]`; } else { selector = `[${attribute.name}=${attribute.valueSpan?.toString() ?? ''}]`; @@ -182,18 +181,18 @@ function toAttributeCssSelector(attribute: t.TextAttribute|t.BoundAttribute|t.Bo return selector.replace(/\$/g, '\\$'); } -function getNodeName(node: t.Template|t.Element): string { - return node instanceof t.Template ? (node.tagName ?? 'ng-template') : node.name; +function getNodeName(node: TmplAstTemplate|TmplAstElement): string { + return node instanceof TmplAstTemplate ? (node.tagName ?? 'ng-template') : node.name; } /** * Given a template or element node, returns all attributes on the node. */ -function getAttributes(node: t.Template| - t.Element): Array { - const attributes: Array = +function getAttributes(node: TmplAstTemplate|TmplAstElement): + Array { + const attributes: Array = [...node.attributes, ...node.inputs, ...node.outputs]; - if (node instanceof t.Template) { + if (node instanceof TmplAstTemplate) { attributes.push(...node.templateAttrs); } return attributes; @@ -225,7 +224,7 @@ function difference(left: Set, right: Set): Set { */ // TODO(atscott): Add unit tests for this and the one for attributes export function getDirectiveMatchesForElementTag( - element: t.Template|t.Element, directives: T[]): Set { + element: TmplAstTemplate|TmplAstElement, directives: T[]): Set { const attributes = getAttributes(element); const allAttrs = attributes.map(toAttributeCssSelector); const allDirectiveMatches = @@ -235,7 +234,7 @@ export function getDirectiveMatchesForElementTag { const attributes = getAttributes(hostNode); const allAttrs = attributes.map(toAttributeCssSelector); @@ -319,9 +318,9 @@ export function filterAliasImports(displayParts: ts.SymbolDisplayPart[]): ts.Sym }); } -export function isDollarEvent(n: t.Node|e.AST): n is e.PropertyRead { - return n instanceof e.PropertyRead && n.name === '$event' && - n.receiver instanceof e.ImplicitReceiver && !(n.receiver instanceof e.ThisReceiver); +export function isDollarEvent(n: TmplAstNode|AST): n is PropertyRead { + return n instanceof PropertyRead && n.name === '$event' && + n.receiver instanceof ImplicitReceiver && !(n.receiver instanceof ThisReceiver); } /** @@ -386,18 +385,18 @@ export function getTemplateLocationFromTcbLocation( return {templateUrl, span}; } -export function isBoundEventWithSyntheticHandler(event: t.BoundEvent): boolean { +export function isBoundEventWithSyntheticHandler(event: TmplAstBoundEvent): boolean { // An event binding with no value (e.g. `(event|)`) parses to a `BoundEvent` with a // `LiteralPrimitive` handler with value `'ERROR'`, as opposed to a property binding with no // value which has an `EmptyExpr` as its value. This is a synthetic node created by the binding // parser, and is not suitable to use for Language Service analysis. Skip it. // // TODO(alxhub): modify the parser to generate an `EmptyExpr` instead. - let handler: e.AST = event.handler; - if (handler instanceof e.ASTWithSource) { + let handler: AST = event.handler; + if (handler instanceof ASTWithSource) { handler = handler.ast; } - if (handler instanceof e.LiteralPrimitive && handler.value === 'ERROR') { + if (handler instanceof LiteralPrimitive && handler.value === 'ERROR') { return true; } return false; From 0ecf5157989193878030278a3fc8e87976e03ff8 Mon Sep 17 00:00:00 2001 From: Jessica Janiuk Date: Tue, 5 Mar 2024 11:17:59 -0500 Subject: [PATCH 2/7] ci: fix ng-dev build for pr merging (#54707) This updates ng-dev to the latest hash fixing the merge files query. PR Close #54707 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index df497f79425dd..b71faaa3b580a 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@angular/animations": "^17.2.0-next", "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#65f8e0021b37719f1d6352d0680c6b45a47a6b3a", "@angular/docs": "https://github.com/angular/dev-infra-private-docs-builds.git#5274bdd3611a27067888e9a93c0838ab776e43af", - "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#0126481d9074759b2d1c5be77f8a3c87e5c3c1e4", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#262c6ede0815bb2ba6fbe6f1790eaaa77ce84c9c", "@babel/helper-remap-async-to-generator": "^7.18.9", "@babel/plugin-proposal-async-generator-functions": "^7.20.7", "@bazel/bazelisk": "^1.7.5", diff --git a/yarn.lock b/yarn.lock index 15396468016c8..74281bf9eb143 100644 --- a/yarn.lock +++ b/yarn.lock @@ -546,9 +546,9 @@ "@material/typography" "15.0.0-canary.7f224ddd4.0" tslib "^2.3.0" -"@angular/ng-dev@https://github.com/angular/dev-infra-private-ng-dev-builds.git#0126481d9074759b2d1c5be77f8a3c87e5c3c1e4": - version "0.0.0-b18378deb8556573a8dc7f841415260bccfba878" - resolved "https://github.com/angular/dev-infra-private-ng-dev-builds.git#0126481d9074759b2d1c5be77f8a3c87e5c3c1e4" +"@angular/ng-dev@https://github.com/angular/dev-infra-private-ng-dev-builds.git#262c6ede0815bb2ba6fbe6f1790eaaa77ce84c9c": + version "0.0.0-96a8277d21eb61a2370061717ffa8dee5668caa0" + resolved "https://github.com/angular/dev-infra-private-ng-dev-builds.git#262c6ede0815bb2ba6fbe6f1790eaaa77ce84c9c" dependencies: "@yarnpkg/lockfile" "^1.1.0" typescript "~4.9.0" From 70996ed8d38a920a9512d306c3af9f7cb59b4641 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Tue, 27 Feb 2024 12:31:49 +0100 Subject: [PATCH 3/7] docs(docs-infra): remove unwanted styling on API page. (#54621) `box-sizing: border-box` prevents overflow caused by borders. PR Close #54621 --- .../api-reference-details-page.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss index 0397118c107e2..eeb6e3106894e 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss @@ -104,6 +104,7 @@ } & > .docs-code { + box-sizing: border-box; width: 100%; max-height: 93vh; overflow: hidden; From 40424fc4b6b19b98220c4e99928e4a6c75412447 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Wed, 28 Feb 2024 14:46:37 +0100 Subject: [PATCH 4/7] docs: fix label on API link. (#54621) Remove the unecessary double quotes on a @see link. PR Close #54621 --- packages/core/src/di/interface/provider.ts | 24 +++++++++++----------- packages/core/src/di/metadata.ts | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/core/src/di/interface/provider.ts b/packages/core/src/di/interface/provider.ts index e3dd959df1177..15478db2ee4cf 100644 --- a/packages/core/src/di/interface/provider.ts +++ b/packages/core/src/di/interface/provider.ts @@ -23,7 +23,7 @@ export interface ValueSansProvider { /** * Configures the `Injector` to return a value for a token. - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -72,7 +72,7 @@ export interface StaticClassSansProvider { /** * Configures the `Injector` to return an instance of `useClass` for a token. - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -104,7 +104,7 @@ export interface StaticClassProvider extends StaticClassSansProvider { /** * Configures the `Injector` to return an instance of a token. * - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -125,7 +125,7 @@ export interface ConstructorSansProvider { /** * Configures the `Injector` to return an instance of a token. * - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -154,7 +154,7 @@ export interface ConstructorProvider extends ConstructorSansProvider { * Configures the `Injector` to return a value of another `useExisting` token. * * @see {@link ExistingProvider} - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @publicApi */ @@ -168,7 +168,7 @@ export interface ExistingSansProvider { /** * Configures the `Injector` to return a value of another `useExisting` token. * - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -197,7 +197,7 @@ export interface ExistingProvider extends ExistingSansProvider { * Configures the `Injector` to return a value by invoking a `useFactory` function. * * @see {@link FactoryProvider} - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @publicApi */ @@ -217,7 +217,7 @@ export interface FactorySansProvider { /** * Configures the `Injector` to return a value by invoking a `useFactory` function. - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -251,7 +251,7 @@ export interface FactoryProvider extends FactorySansProvider { * A static provider provides tokens to an injector for various types of dependencies. * * @see {@link Injector.create()} - * @see ["Dependency Injection Guide"](guide/dependency-injection-providers). + * @see [Dependency Injection Guide](guide/dependency-injection-providers). * * @publicApi */ @@ -279,7 +279,7 @@ export interface TypeProvider extends Type {} * Configures the `Injector` to return a value by invoking a `useClass` function. * Base for `ClassProvider` decorator. * - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @publicApi */ @@ -292,7 +292,7 @@ export interface ClassSansProvider { /** * Configures the `Injector` to return an instance of `useClass` for a token. - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @usageNotes * @@ -323,7 +323,7 @@ export interface ClassProvider extends ClassSansProvider { /** * Describes how the `Injector` should be configured. - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). * * @see {@link StaticProvider} * diff --git a/packages/core/src/di/metadata.ts b/packages/core/src/di/metadata.ts index 859b4273ae1dd..65ad7e49117f9 100644 --- a/packages/core/src/di/metadata.ts +++ b/packages/core/src/di/metadata.ts @@ -32,7 +32,7 @@ export interface InjectDecorator { * * * - * @see ["Dependency Injection Guide"](guide/dependency-injection) + * @see [Dependency Injection Guide](guide/dependency-injection) * */ (token: any): any; @@ -83,7 +83,7 @@ export interface OptionalDecorator { * * * - * @see ["Dependency Injection Guide"](guide/dependency-injection). + * @see [Dependency Injection Guide](guide/dependency-injection). */ (): any; new(): Optional; From b5366f20d9382753f6acdffefd21dfb158c3753d Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Wed, 28 Feb 2024 21:56:32 +0100 Subject: [PATCH 5/7] docs: fix tutorial preview size (#54621) PR Close #54621 --- adev/src/app/editor/embedded-editor.component.scss | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/adev/src/app/editor/embedded-editor.component.scss b/adev/src/app/editor/embedded-editor.component.scss index 155281d495560..8c569484eb2e1 100644 --- a/adev/src/app/editor/embedded-editor.component.scss +++ b/adev/src/app/editor/embedded-editor.component.scss @@ -145,7 +145,9 @@ $width-breakpoint: 950px; display: flex; align-items: center; background-color: var(--octonary-contrast); - transition: background-color 0.3s ease, border-color 0.3s ease; + transition: + background-color 0.3s ease, + border-color 0.3s ease; i { color: var(--bright-blue); @@ -192,3 +194,11 @@ $width-breakpoint: 950px; padding: 1.5rem; } } + +::ng-deep mat-tab-group { + .mat-mdc-tab-body-wrapper, + .mat-mdc-tab-body, + .mat-mdc-tab-body-content { + display: contents; + } +} From d2ec5b617889604ffb6667ba5b873de71fcff691 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Wed, 28 Feb 2024 22:27:58 +0100 Subject: [PATCH 6/7] docs: fix clipped `docs-code` block in `docs-reference-api-tab` (#54621) PR Close #54621 --- .../api-reference-details-page.component.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss index eeb6e3106894e..621d7d741ca71 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss @@ -99,6 +99,8 @@ display: flex; gap: 1.81rem; align-items: flex-start; + margin-bottom: 1px; + @include mq.for-desktop-down { flex-direction: column; } From 755390ace3a7b73564e9a6f245d2c3fc2218e953 Mon Sep 17 00:00:00 2001 From: Jessica Janiuk Date: Thu, 29 Feb 2024 14:25:19 -0500 Subject: [PATCH 7/7] docs: add process documentation to CARETAKER.md (#54664) This adds documentation regarding the latest changes to framework merge tools. It specifically covers the requires: TGP label and separate sync for primitives. PR Close #54664 --- docs/CARETAKER.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/CARETAKER.md b/docs/CARETAKER.md index 639d6b6716478..cfc1deabe415a 100644 --- a/docs/CARETAKER.md +++ b/docs/CARETAKER.md @@ -21,6 +21,29 @@ To merge a PR run: $ yarn ng-dev pr merge ``` +## Primitives and Blocked merges + +The caretaker may encounter PRs that will fail to merge due to primitives files. Code inside some +paths must be merged and synced separately. For example, anything under `//packages/core/primitives` +has to be merged and synced separately from other changes. Once the latest sync has fully landed, +merging can continue. This is to reduce the risk of challenging rollbacks in the event of a breakage. + +## PRs that require TGPs + +If a PR is risky or otherwise requires more thorough testing, add the `requires: TGP` label to the PR. +The merge tooling will enforce that a TGP has been run, or alternatively you can add a review comment +that starts with `TESTED=` and then put a reason why the PR is sufficiently tested. This will allow +the PR to be merged. The `requires: TGP` label will be automatically added to PRs that affect files +matching `separateFilePatterns` in [`.ng-dev/google-sync-config.json`](https://github.com/angular/angular/blob/main/.ng-dev/google-sync-config.json). + +For example: + +``` +TESTED=docs only update and does not need a TGP +``` + +**Note:** the review comment _must_ be made by a googler. + ### Recovering from failed `merge-pr` due to conflicts The `ng-dev pr merge` tool will automatically restore to the previous git state when a merge fails.