Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
atscott committed Oct 20, 2023
1 parent f32eb86 commit 5bfe9ac
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 31 deletions.
26 changes: 19 additions & 7 deletions packages/compiler/src/render3/r3_ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ export class ViewportDeferredTrigger extends DeferredTrigger {

export class DeferredBlockPlaceholder implements Node {
constructor(
public children: Node[], public minimumTime: number|null, public sourceSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}
public children: Node[], public minimumTime: number|null, public nameSpan: ParseSourceSpan,
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan|null) {}

visit<Result>(visitor: Visitor<Result>): Result {
return visitor.visitDeferredBlockPlaceholder(this);
Expand All @@ -172,8 +173,8 @@ export class DeferredBlockPlaceholder implements Node {
export class DeferredBlockLoading implements Node {
constructor(
public children: Node[], public afterTime: number|null, public minimumTime: number|null,
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan|null) {}
public nameSpan: ParseSourceSpan, public sourceSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}

visit<Result>(visitor: Visitor<Result>): Result {
return visitor.visitDeferredBlockLoading(this);
Expand All @@ -182,7 +183,7 @@ export class DeferredBlockLoading implements Node {

export class DeferredBlockError implements Node {
constructor(
public children: Node[], public sourceSpan: ParseSourceSpan,
public children: Node[], public nameSpan: ParseSourceSpan, public sourceSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {}

visit<Result>(visitor: Visitor<Result>): Result {
Expand Down Expand Up @@ -210,8 +211,9 @@ export class DeferredBlock implements Node {
public children: Node[], triggers: DeferredBlockTriggers,
prefetchTriggers: DeferredBlockTriggers, public placeholder: DeferredBlockPlaceholder|null,
public loading: DeferredBlockLoading|null, public error: DeferredBlockError|null,
public sourceSpan: ParseSourceSpan, public mainBlockSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {
public nameSpan: ParseSourceSpan, public sourceSpan: ParseSourceSpan,
public mainBlockSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan|null) {
this.triggers = triggers;
this.prefetchTriggers = prefetchTriggers;
// We cache the keys since we know that they won't change and we
Expand Down Expand Up @@ -496,3 +498,13 @@ export function visitAll<Result>(visitor: Visitor<Result>, nodes: Node[]): Resul
}
return result;
}

export type BlockNode = IfBlockBranch|ForLoopBlockEmpty|ForLoopBlock|SwitchBlockCase|SwitchBlock|
DeferredBlockError|DeferredBlockPlaceholder|DeferredBlockLoading;
export function isBlockNode(node: Node): node is BlockNode {
return node instanceof IfBlockBranch || node instanceof ForLoopBlockEmpty ||
node instanceof ForLoopBlock || node instanceof SwitchBlockCase ||
node instanceof SwitchBlock || node instanceof DeferredBlockError ||
node instanceof DeferredBlockPlaceholder || node instanceof DeferredBlockLoading ||
node instanceof DeferredBlock
}
20 changes: 10 additions & 10 deletions packages/compiler/src/render3/r3_deferred_blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ export function createDeferredBlock(
const {triggers, prefetchTriggers} =
parsePrimaryTriggers(ast.parameters, bindingParser, errors, placeholder);

// The `defer` block has a main span encompassing all of the connected branches as well. For the
// span of only the first "main" branch, use `mainSourceSpan`.
// The `defer` block has a main span encompassing all of the connected branches as well.
let lastEndSourceSpan = ast.endSourceSpan;
let endOfLastSourceSpan = ast.sourceSpan.end;
if (connectedBlocks.length > 0) {
Expand All @@ -58,12 +57,13 @@ export function createDeferredBlock(
endOfLastSourceSpan = lastConnectedBlock.sourceSpan.end;
}

const mainDeferredSourceSpan = new ParseSourceSpan(ast.sourceSpan.start, endOfLastSourceSpan);
const sourceSpanWithConnectedBlocks =
new ParseSourceSpan(ast.sourceSpan.start, endOfLastSourceSpan);

const node = new t.DeferredBlock(
html.visitAll(visitor, ast.children, ast.children), triggers, prefetchTriggers, placeholder,
loading, error, mainDeferredSourceSpan, ast.sourceSpan, ast.startSourceSpan,
lastEndSourceSpan);
loading, error, ast.nameSpan, sourceSpanWithConnectedBlocks, ast.sourceSpan,
ast.startSourceSpan, lastEndSourceSpan);

return {node, errors};
}
Expand Down Expand Up @@ -140,7 +140,7 @@ function parsePlaceholderBlock(ast: html.Block, visitor: html.Visitor): t.Deferr
}

return new t.DeferredBlockPlaceholder(
html.visitAll(visitor, ast.children, ast.children), minimumTime, ast.sourceSpan,
html.visitAll(visitor, ast.children, ast.children), minimumTime, ast.nameSpan, ast.sourceSpan,
ast.startSourceSpan, ast.endSourceSpan);
}

Expand Down Expand Up @@ -181,8 +181,8 @@ function parseLoadingBlock(ast: html.Block, visitor: html.Visitor): t.DeferredBl
}

return new t.DeferredBlockLoading(
html.visitAll(visitor, ast.children, ast.children), afterTime, minimumTime, ast.sourceSpan,
ast.startSourceSpan, ast.endSourceSpan);
html.visitAll(visitor, ast.children, ast.children), afterTime, minimumTime, ast.nameSpan,
ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
}


Expand All @@ -192,8 +192,8 @@ function parseErrorBlock(ast: html.Block, visitor: html.Visitor): t.DeferredBloc
}

return new t.DeferredBlockError(
html.visitAll(visitor, ast.children, ast.children), ast.sourceSpan, ast.startSourceSpan,
ast.endSourceSpan);
html.visitAll(visitor, ast.children, ast.children), ast.nameSpan, ast.sourceSpan,
ast.startSourceSpan, ast.endSourceSpan);
}

function parsePrimaryTriggers(
Expand Down
3 changes: 1 addition & 2 deletions packages/language-service/src/language_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ export class LanguageService {
const node = positionDetails.context.kind === TargetNodeKind.TwoWayBindingContext ?
positionDetails.context.nodes[0] :
positionDetails.context.node;
return new QuickInfoBuilder(
this.tsLS, compiler, templateInfo.component, node, positionDetails.parent)
return new QuickInfoBuilder(this.tsLS, compiler, templateInfo.component, node, positionDetails)
.get();
}

Expand Down
10 changes: 2 additions & 8 deletions packages/language-service/src/outlining_spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ export function getOutliningSpans(compiler: NgCompiler, fileName: string): ts.Ou
}

class BlockVisitor extends t.RecursiveVisitor {
readonly blocks = [] as
Array<t.IfBlockBranch|t.ForLoopBlockEmpty|t.ForLoopBlock|t.SwitchBlockCase|t.SwitchBlock|
t.DeferredBlockError|t.DeferredBlockPlaceholder|t.DeferredBlockLoading>;
readonly blocks = [] as Array<t.BlockNode>;

static getBlockSpans(templateNodes: t.Node[]): ts.OutliningSpan[] {
const visitor = new BlockVisitor();
Expand Down Expand Up @@ -78,11 +76,7 @@ class BlockVisitor extends t.RecursiveVisitor {
}

visit(node: t.Node) {
if (node instanceof t.IfBlockBranch || node instanceof t.ForLoopBlockEmpty ||
node instanceof t.ForLoopBlock || node instanceof t.SwitchBlockCase ||
node instanceof t.SwitchBlock || node instanceof t.DeferredBlockError ||
node instanceof t.DeferredBlockPlaceholder || node instanceof t.DeferredBlockLoading ||
node instanceof t.DeferredBlock) {
if (t.isBlockNode(node)) {
this.blocks.push(node);
}
}
Expand Down
64 changes: 60 additions & 4 deletions packages/language-service/src/quick_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@
* 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, PropertyRead, ThisReceiver, TmplAstBoundAttribute, TmplAstNode, TmplAstTextAttribute} from '@angular/compiler';
import {AST, Call, ImplicitReceiver, PropertyRead, ThisReceiver, TmplAstBoundAttribute, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, 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, isBlockNode} from '@angular/compiler/src/render3/r3_ast';
import ts from 'typescript';

import {createDisplayParts, DisplayInfoKind, SYMBOL_PUNC, SYMBOL_SPACE, SYMBOL_TEXT, unsafeCastDisplayInfoKindToScriptElementKind} from './display_parts';
import {filterAliasImports, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTextSpanOfNode} from './utils';
import {TemplateTarget} from './template_target';
import {filterAliasImports, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTextSpanOfNode, isWithin, toTextSpan} from './utils';

export class QuickInfoBuilder {
private readonly typeChecker = this.compiler.getCurrentProgram().getTypeChecker();
private readonly parent = this.positionDetails.parent;

constructor(
private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler,
private readonly component: ts.ClassDeclaration, private node: TmplAstNode|AST,
private parent: TmplAstNode|AST|null) {}
private readonly positionDetails: TemplateTarget) {}

get(): ts.QuickInfo|undefined {
if (!(this.node instanceof AST) && isBlockNode(this.node)) {
return createQuickInfoForBlock(this.node, this.positionDetails.position);
}
const symbol =
this.compiler.getTemplateTypeChecker().getSymbolOfNode(this.node, this.component);
if (symbol !== null) {
Expand Down Expand Up @@ -243,7 +249,7 @@ function createNgTemplateQuickInfo(node: TmplAstNode|AST): ts.QuickInfo {
*/
export function createQuickInfo(
name: string, kind: DisplayInfoKind, textSpan: ts.TextSpan, containerName?: string,
type?: string, documentation?: ts.SymbolDisplayPart[]): ts.QuickInfo {
type?: string, documentation?: ts.SymbolDisplayPart[], tags?: ts.JSDocTagInfo[]): ts.QuickInfo {
const displayParts = createDisplayParts(name, kind, containerName, type);

return {
Expand All @@ -252,5 +258,55 @@ export function createQuickInfo(
textSpan: textSpan,
displayParts,
documentation,
tags,
};
}
function createQuickInfoForBlock(node: BlockNode, cursorPositionInTemplate: number): ts.QuickInfo|
undefined {
if ((node instanceof TmplAstDeferredBlock || node instanceof TmplAstDeferredBlockError ||
node instanceof TmplAstDeferredBlockLoading ||
node instanceof TmplAstDeferredBlockPlaceholder) &&
isWithin(cursorPositionInTemplate, node.nameSpan)) {
const blockName = node.nameSpan.toString().trim();
const blockInfo = BLOCK_NAME_TO_DOC_MAP[blockName];
const linkTags: ts.JSDocTagInfo[] =
(blockInfo?.links ?? []).map(text => ({text: [{kind: SYMBOL_TEXT, text}], name: 'see'}));
return createQuickInfo(
blockName,
DisplayInfoKind.BLOCK,
toTextSpan(node.nameSpan),
/** containerName */ undefined,
/** type */ undefined,
[{
kind: SYMBOL_TEXT,
text: blockInfo?.docString ?? '',
}],
linkTags,
);
}
return undefined;
}

const BLOCK_NAME_TO_DOC_MAP: {[blockName: string]: {docString: string, links: string[]}} = {
'@defer': {
docString: `
# Defer
Lazy loads the html in the block.
`,
links: ['[AIO Reference](https://next.angular.io/api/core/defer)']
},
'@placeholder': {
docString: `Placeholder content to display when the defer condition is not 'true'.`,
links: ['[AIO Reference](https://next.angular.io/api/core/defer)']
},
'@error': {
docString:
`Content to render if there is an error loading or rendering the defer block content.`,
links: ['[AIO Reference](https://next.angular.io/api/core/defer)']
},
'@loading': {
docString: `Content to render while the deferred block is being downloaded.`,
links: ['[AIO Reference](https://next.angular.io/api/core/defer)']
}
};
42 changes: 42 additions & 0 deletions packages/language-service/test/quick_info_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,48 @@ describe('quick info', () => {
});
});

fdescribe('blocks', () => {
it('defer', () => {
expectQuickInfo({
templateOverride: `@de¦fer { } @placeholder { <input /> }`,
expectedSpanText: '@defer ',
expectedDisplayString: '(block) @defer'
});
});

it('defer with condition', () => {
expectQuickInfo({
templateOverride: `@de¦fer (on viewport) { } @placeholder { <input /> }`,
expectedSpanText: '@defer ',
expectedDisplayString: '(block) @defer'
});
});

it('placeholder', () => {
expectQuickInfo({
templateOverride: `@defer { } @pla¦ceholder { <input /> }`,
expectedSpanText: '@placeholder ',
expectedDisplayString: '(block) @placeholder'
});
});

it('loading', () => {
expectQuickInfo({
templateOverride: `@defer { } @loadin¦g { <input /> }`,
expectedSpanText: '@loading ',
expectedDisplayString: '(block) @loading'
});
});

it('error', () => {
expectQuickInfo({
templateOverride: `@defer { } @erro¦r { <input /> }`,
expectedSpanText: '@error ',
expectedDisplayString: '(block) @error'
});
});
});

it('should work for object literal with shorthand property declarations', () => {
initMockFileSystem('Native');
env = LanguageServiceTestEnv.setup();
Expand Down

0 comments on commit 5bfe9ac

Please sign in to comment.