Skip to content

Commit

Permalink
fix(language-service): diagnostic and definition should work for abso…
Browse files Browse the repository at this point in the history
…lute url (#40406)

This commit fixes a bug in the **View Engine** implementation of
`getSemanticDiagnostics` and `getDefinitionAndBoundSpan` for node in the
decorator metadata that represents an external URL
(`templateUrl` or `styleUrls`).

The URL could be either relative or absolute, but the latter was not taken
into account.

Fix angular/vscode-ng-language-service#1055

PR Close #40406
  • Loading branch information
kyliau authored and atscott committed Jan 13, 2021
1 parent b48eabd commit 625d2c2
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 8 deletions.
5 changes: 2 additions & 3 deletions packages/language-service/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import * as path from 'path';
import * as ts from 'typescript'; // used as value and is provided at runtime

import {locateSymbols} from './locate_symbol';
import {findTightestNode, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './ts_utils';
import {AstResult, Span} from './types';
import {extractAbsoluteFilePath} from './utils';

/**
* Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and
Expand Down Expand Up @@ -59,10 +59,9 @@ function getUrlFromProperty(
return;
}

const sf = urlNode.getSourceFile();
// Extract url path specified by the url node, which is relative to the TypeScript source file
// the url node is defined in.
const url = path.join(path.dirname(sf.fileName), urlNode.text);
const url = extractAbsoluteFilePath(urlNode);

// If the file does not exist, bail. It is possible that the TypeScript language service host
// does not have a `fileExists` method, in which case optimistically assume the file exists.
Expand Down
7 changes: 3 additions & 4 deletions packages/language-service/src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
*/

import {NgAnalyzedModules} from '@angular/compiler';
import * as path from 'path';
import * as ts from 'typescript';

import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {getTemplateExpressionDiagnostics} from './expression_diagnostics';
import {findPropertyValueOfType, findTightestNode} from './ts_utils';
import * as ng from './types';
import {TypeScriptServiceHost} from './typescript_host';
import {offsetSpan, spanOf} from './utils';
import {extractAbsoluteFilePath, offsetSpan, spanOf} from './utils';

/**
* Return diagnostic information for the parsed AST of the template.
Expand Down Expand Up @@ -161,8 +160,8 @@ function validateUrls(
// picked up by the TS Language Server.
continue;
}
const curPath = urlNode.getSourceFile().fileName;
const url = path.join(path.dirname(curPath), urlNode.text);

const url = extractAbsoluteFilePath(urlNode);
if (tsLsHost.fileExists(url)) continue;

// Exclude opening and closing quotes in the url span.
Expand Down
13 changes: 12 additions & 1 deletion packages/language-service/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
*/

import {AstPath, BoundEventAst, CompileDirectiveSummary, CompileTypeMetadata, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, HtmlAstPath, identifierName, Identifiers, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, RecursiveVisitor, TemplateAst, TemplateAstPath, templateVisitAll, visitAll} from '@angular/compiler';
import {getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './ts_utils';
import * as path from 'path';

import {AstResult, DiagnosticTemplateInfo, SelectorInfo, Span, Symbol, SymbolQuery} from './types';

interface SpanHolder {
Expand Down Expand Up @@ -204,3 +205,13 @@ export function findOutputBinding(
}
}
}

/**
* Returns an absolute path from the text in `node`. If the text is already
* an absolute path, return it as is, otherwise join the path with the filename
* of the source file.
*/
export function extractAbsoluteFilePath(node: ts.StringLiteralLike) {
const url = node.text;
return path.isAbsolute(url) ? url : path.join(path.dirname(node.getSourceFile().fileName), url);
}
13 changes: 13 additions & 0 deletions packages/language-service/test/definitions_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,19 @@ describe('definitions', () => {
expect(def.textSpan).toEqual({start: 0, length: 0});
});

it('should be able to find a template from an absolute url', () => {
const fileName = mockHost.addCode(`
@Component({
templateUrl: '${TEST_TEMPLATE}',
})
export class MyComponent {}`);

const marker = mockHost.readFile(fileName)!.indexOf(TEST_TEMPLATE);
const result = ngService.getDefinitionAndBoundSpan(fileName, marker);

expect(result?.definitions?.[0].fileName).toBe(TEST_TEMPLATE);
});

it('should be able to find a stylesheet from a url', () => {
const fileName = mockHost.addCode(`
@Component({
Expand Down
13 changes: 13 additions & 0 deletions packages/language-service/test/diagnostics_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ describe('diagnostics', () => {
}
});

it('should not produce diagnostics for absolute template url', () => {
mockHost.override(APP_COMPONENT, `
import {Component} from '@angular/core';
@Component({
templateUrl: '${TEST_TEMPLATE}',
})
export class AppComponent {}
`);
const diags = ngLS.getSemanticDiagnostics(APP_COMPONENT);
expect(diags).toEqual([]);
});

it('should not produce diagnostics for slice pipe with arguments', () => {
mockHost.override(TEST_TEMPLATE, `
<div *ngFor="let h of heroes | slice:0:1">
Expand Down

0 comments on commit 625d2c2

Please sign in to comment.