Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "//:pnpm-lock.yaml").
# This file should be checked into version control along with the pnpm-lock.yaml file.
.npmrc=974837034
pnpm-lock.yaml=-1736799033
pnpm-lock.yaml=-60247795
yarn.lock=1176905511
package.json=-1064085518
package.json=-552185186
pnpm-workspace.yaml=1711114604
12 changes: 6 additions & 6 deletions client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {OpenOutputChannel, ProjectLoadingFinish, ProjectLoadingStart, SuggestStr
import {GetComponentsWithTemplateFile, GetTcbRequest, GetTemplateLocationForComponent, IsInAngularProject} from '../../common/requests';
import {NodeModule, resolve} from '../../common/resolver';

import {isInsideStringLiteral, isNotTypescriptOrInsideComponentDecorator} from './embedded_support';
import {isInsideStringLiteral, isNotTypescriptOrSupportedDecoratorField} from './embedded_support';

interface GetTcbResponse {
uri: vscode.Uri;
Expand Down Expand Up @@ -91,23 +91,23 @@ export class AngularLanguageClient implements vscode.Disposable {
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: lsp.ProvideDefinitionSignature) => {
if (await this.isInAngularProject(document) &&
isNotTypescriptOrInsideComponentDecorator(document, position)) {
isNotTypescriptOrSupportedDecoratorField(document, position)) {
return next(document, position, token);
}
},
provideTypeDefinition: async (
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next) => {
if (await this.isInAngularProject(document) &&
isNotTypescriptOrInsideComponentDecorator(document, position)) {
isNotTypescriptOrSupportedDecoratorField(document, position)) {
return next(document, position, token);
}
},
provideHover: async (
document: vscode.TextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: lsp.ProvideHoverSignature) => {
if (!(await this.isInAngularProject(document)) ||
!isNotTypescriptOrInsideComponentDecorator(document, position)) {
!isNotTypescriptOrSupportedDecoratorField(document, position)) {
return;
}

Expand All @@ -131,7 +131,7 @@ export class AngularLanguageClient implements vscode.Disposable {
context: vscode.SignatureHelpContext, token: vscode.CancellationToken,
next: lsp.ProvideSignatureHelpSignature) => {
if (await this.isInAngularProject(document) &&
isNotTypescriptOrInsideComponentDecorator(document, position)) {
isNotTypescriptOrSupportedDecoratorField(document, position)) {
return next(document, position, context, token);
}
},
Expand All @@ -141,7 +141,7 @@ export class AngularLanguageClient implements vscode.Disposable {
next: lsp.ProvideCompletionItemsSignature) => {
// If not in inline template, do not perform request forwarding
if (!(await this.isInAngularProject(document)) ||
!isNotTypescriptOrInsideComponentDecorator(document, position)) {
!isNotTypescriptOrSupportedDecoratorField(document, position)) {
return;
}
const angularCompletionsPromise = next(document, position, context, token) as
Expand Down
22 changes: 16 additions & 6 deletions client/src/embedded_support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@
import * as ts from 'typescript';
import * as vscode from 'vscode';

/** Determines if the position is inside an inline template, templateUrl, or string in styleUrls. */
export function isNotTypescriptOrInsideComponentDecorator(
const ANGULAR_PROPERTY_ASSIGNMENTS = new Set([
'template',
'templateUrl',
'styleUrls',
'styleUrl',
'host',
]);

/**
* Determines if the position is inside a decorator
* property that supports language service features.
*/
export function isNotTypescriptOrSupportedDecoratorField(
document: vscode.TextDocument, position: vscode.Position): boolean {
if (document.languageId !== 'typescript') {
return true;
}
return isPropertyAssignmentToStringOrStringInArray(
document.getText(), document.offsetAt(position),
['template', 'templateUrl', 'styleUrls', 'styleUrl']);
document.getText(), document.offsetAt(position), ANGULAR_PROPERTY_ASSIGNMENTS);
}

/**
Expand Down Expand Up @@ -62,7 +72,7 @@ export function isInsideStringLiteral(
* https://github.com/Microsoft/TypeScript/issues/20055
*/
function isPropertyAssignmentToStringOrStringInArray(
documentText: string, offset: number, propertyAssignmentNames: string[]): boolean {
documentText: string, offset: number, propertyAssignmentNames: Set<string>): boolean {
const scanner = ts.createScanner(ts.ScriptTarget.ESNext, true /* skipTrivia */);
scanner.setText(documentText);

Expand All @@ -74,7 +84,7 @@ function isPropertyAssignmentToStringOrStringInArray(
let propertyAssignmentContext = false;
while (token !== ts.SyntaxKind.EndOfFileToken && scanner.getStartPos() < offset) {
if (lastToken === ts.SyntaxKind.Identifier && lastTokenText !== undefined &&
propertyAssignmentNames.includes(lastTokenText) && token === ts.SyntaxKind.ColonToken) {
token === ts.SyntaxKind.ColonToken && propertyAssignmentNames.has(lastTokenText)) {
propertyAssignmentContext = true;
token = scanner.scan();
continue;
Expand Down
3 changes: 2 additions & 1 deletion integration/project/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"angularCompilerOptions": {
"strictTemplates": true,
"typeCheckHostBindings": true,
"strictInjectionParameters": true
}
}
}
3 changes: 2 additions & 1 deletion integration/workspace/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
"strictTemplates": true,
"typeCheckHostBindings": true
}
}
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@
"source.ts"
]
},
{
"path": "./syntaxes/host-object-literal.json",
"scopeName": "host-object-literal.ng",
"injectTo": [
"source.ts"
],
"embeddedLanguages": {
"text.html.derivative": "html",
"expression.ng": "javascript",
"source.ts": "typescript"
}
},
{
"path": "./syntaxes/template-tag.json",
"scopeName": "template.tag.ng",
Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions syntaxes/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ js_run_binary(
"_template-blocks.json",
"_template-tag.json",
"_let-declaration.json",
"_host-object-literal.json",
]
)

Expand All @@ -29,6 +30,7 @@ write_source_files(
"template-blocks.json": "_template-blocks.json",
"template-tag.json": "_template-tag.json",
"let-declaration.json": "_let-declaration.json",
"host-object-literal.json": "_host-object-literal.json",
}
)

Expand Down
102 changes: 102 additions & 0 deletions syntaxes/host-object-literal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"scopeName": "host-object-literal.ng",
"injectionSelector": "L:meta.decorator.ts -comment -text.html -expression.ng",
"patterns": [
{
"include": "#hostObjectLiteral"
}
],
"repository": {
"hostObjectLiteral": {
"begin": "(host)\\s*(:)\\s*{",
"beginCaptures": {
"1": {
"name": "meta.object-literal.key.ts"
},
"2": {
"name": "meta.object-literal.key.ts punctuation.separator.key-value.ts"
}
},
"contentName": "hostbindings.ng",
"end": "}",
"patterns": [
{
"include": "#ngHostBindingDynamic"
},
{
"include": "#ngHostBindingStatic"
},
{
"include": "source.ts"
}
]
},
"ngHostBindingDynamic": {
"begin": "\\s*('|\")([\\[(].*?[\\])])(\\1)(:)",
"beginCaptures": {
"1": {
"name": "string"
},
"2": {
"name": "entity.other.attribute-name.html"
},
"3": {
"name": "string"
},
"4": {
"name": "meta.object-literal.key.ts punctuation.separator.key-value.ts"
}
},
"contentName": "hostbinding.dynamic.ng",
"patterns": [
{
"include": "#ngHostBindingDynamicValue"
}
],
"end": "(?=,|})"
},
"ngHostBindingDynamicValue": {
"begin": "\\s*(`|'|\")",
"beginCaptures": {
"1": {
"name": "string"
}
},
"patterns": [
{
"include": "expression.ng"
}
],
"end": "\\1",
"endCaptures": {
"0": {
"name": "string"
}
}
},
"ngHostBindingStatic": {
"begin": "\\s*('|\")?(.*?)(\\1)?\\s*:",
"end": "(?=,|})",
"beginCaptures": {
"1": {
"name": "string"
},
"2": {
"name": "entity.other.attribute-name.html"
},
"3": {
"name": "string"
},
"4": {
"name": "meta.object-literal.key.ts punctuation.separator.key-value.ts"
}
},
"contentName": "hostbinding.static.ng",
"patterns": [
{
"include": "source.ts"
}
]
}
}
}
2 changes: 2 additions & 0 deletions syntaxes/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import * as fs from 'fs';

import {Expression} from './expression';
import {HostObjectLiteral} from './host-object-literal';
import {InlineStyles} from './inline-styles';
import {InlineTemplate} from './inline-template';
import {Template} from './template';
Expand Down Expand Up @@ -59,3 +60,4 @@ build(InlineStyles, 'inline-styles');
build(TemplateBlocks, 'template-blocks');
build(TemplateTag, 'template-tag');
build(LetDeclaration, 'let-declaration');
build(HostObjectLiteral, 'host-object-literal');
101 changes: 101 additions & 0 deletions syntaxes/src/host-object-literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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 {GrammarDefinition} from './types';

/** Highlighting definition for the `host` object of a directive or component. */
export const HostObjectLiteral: GrammarDefinition = {
scopeName: 'host-object-literal.ng',
injectionSelector: 'L:meta.decorator.ts -comment -text.html -expression.ng',
patterns: [{include: '#hostObjectLiteral'}],
repository: {
hostObjectLiteral: {
begin: /(host)\s*(:)\s*{/,
beginCaptures: {
// Key is shown as JS syntax.
1: {name: 'meta.object-literal.key.ts'},
// Colon is shown as JS syntax.
2: {name: 'meta.object-literal.key.ts punctuation.separator.key-value.ts'}
},
contentName: 'hostbindings.ng',
end: /}/,
patterns: [
// Try to match host bindings inside the `host`.
{include: '#ngHostBindingDynamic'},
// Try to match a static binding inside the `host`.
{include: '#ngHostBindingStatic'},
// Include the default TS syntax so that anything that doesn't
// match the above will get the default highlighting.
{include: 'source.ts'},
]
},

// A bound property inside `host`, e.g. `[attr.foo]="expr"` or `(click)="handleClick()"`.
ngHostBindingDynamic: {
begin: /\s*('|")([\[(].*?[\])])(\1)(:)/,
beginCaptures: {
// Opening quote is shown as a string. Only allows single and double quotes, no backticks.
1: {name: 'string'},
// Name is shown as an HTML attribute.
2: {name: 'entity.other.attribute-name.html'},
// Closing quote is shown as a string.
3: {name: 'string'},
// Colon is shown as JS syntax.
4: {name: 'meta.object-literal.key.ts punctuation.separator.key-value.ts'}
},
contentName: 'hostbinding.dynamic.ng',
patterns: [
{include: '#ngHostBindingDynamicValue'},
],
end: /(?=,|})/
},

// Value of a bound property inside `host`.
ngHostBindingDynamicValue: {
begin: /\s*(`|'|")/,
beginCaptures: {
// Opening quote is shown as a string. Allows backticks as well.
1: {name: 'string'},
},
patterns: [
// Content is shown as an Angular expression.
{include: 'expression.ng'},
],
// Ends on the same kind of quote as the opening.
// @ts-ignore
end: /\1/,
endCaptures: {
// Closing quote is shown as a string.
0: {name: 'string'},
}
},

// Static value inside `host`.
ngHostBindingStatic: {
// Note that we need to allow both quoted and non-quoted keys.
begin: /\s*('|")?(.*?)(\1)?\s*:/,
end: /(?=,|})/,
beginCaptures: {
// Opening quote is shown as a string. Only allows single and double quotes, no backticks.
1: {name: 'string'},
// Name is shown as an HTML attribute.
2: {name: 'entity.other.attribute-name.html'},
// Closing quote is shown as a string.
3: {name: 'string'},
// Colon is shown as JS syntax.
4: {name: 'meta.object-literal.key.ts punctuation.separator.key-value.ts'},
},
contentName: 'hostbinding.static.ng',
patterns: [
// Use TypeScript highlighting for the value. This allows us to deal
// with things like escaped strings and variables correctly.
{include: 'source.ts'},
]
},
}
};
Loading
Loading