Skip to content

Commit 26fd083

Browse files
atscottthePunderWoman
authored andcommitted
feat(language-server): Add folding range support for inline styles
Adds folding range support for inline styles to the language server
1 parent 2727637 commit 26fd083

File tree

6 files changed

+111
-9
lines changed

6 files changed

+111
-9
lines changed

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-ng-language-service/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"devDependencies": {
2424
"@types/node": "^24.5.2",
2525
"vscode-html-languageservice": "5.6.1",
26+
"vscode-css-languageservice": "6.3.9",
2627
"vscode-languageserver": "7.0.0",
2728
"vscode-languageserver-textdocument": "^1.0.12",
2829
"vscode-uri": "3.1.0"

vscode-ng-language-service/server/src/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ts_project(
1111
"//:node_modules/typescript",
1212
"//vscode-ng-language-service/common",
1313
"//vscode-ng-language-service/server:node_modules/@types/node",
14+
"//vscode-ng-language-service/server:node_modules/vscode-css-languageservice",
1415
"//vscode-ng-language-service/server:node_modules/vscode-html-languageservice",
1516
"//vscode-ng-language-service/server:node_modules/vscode-languageserver",
1617
"//vscode-ng-language-service/server:node_modules/vscode-languageserver-textdocument",

vscode-ng-language-service/server/src/embedded_support.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,60 @@ export function getHTMLVirtualContent(sf: ts.SourceFile): string {
4242
return content;
4343
}
4444

45+
/**
46+
* Takes a TS file and strips out all non-inline style content.
47+
*
48+
* @see {@link getHTMLVirtualContent}
49+
*/
50+
export function getSCSSVirtualContent(sf: ts.SourceFile): string {
51+
const inlineStyleNodes: ts.Node[] = findAllMatchingNodes(sf, isInlineStyleNode);
52+
const documentText = sf.text;
53+
54+
// Create a blank document with same text length
55+
let content = documentText
56+
.split('\n')
57+
.map((line) => {
58+
return ' '.repeat(line.length);
59+
})
60+
.join('\n');
61+
62+
// add back all the inline style regions in-place
63+
for (const region of inlineStyleNodes) {
64+
content =
65+
content.slice(0, region.getStart(sf) + 1) +
66+
documentText.slice(region.getStart(sf) + 1, region.getEnd() - 1) +
67+
content.slice(region.getEnd() - 1);
68+
}
69+
return content;
70+
}
71+
72+
function isInlineStyleNode(node: ts.Node) {
73+
if (!ts.isStringLiteralLike(node)) {
74+
return false;
75+
}
76+
77+
if (isAssignmentToPropertyWithName(node, 'styles')) {
78+
return true;
79+
}
80+
81+
if (
82+
node.parent &&
83+
ts.isArrayLiteralExpression(node.parent) &&
84+
isAssignmentToPropertyWithName(node.parent, 'styles')
85+
) {
86+
return true;
87+
}
88+
89+
return false;
90+
}
91+
92+
function isAssignmentToPropertyWithName(node: ts.Node, propertyName: 'styles' | 'template') {
93+
const assignment = getPropertyAssignmentFromValue(node, propertyName);
94+
return assignment !== null && getClassDeclFromDecoratorProp(assignment) !== null;
95+
}
96+
4597
function isInlineTemplateNode(node: ts.Node) {
46-
const assignment = getPropertyAssignmentFromValue(node, 'template');
47-
return (
48-
ts.isStringLiteralLike(node) &&
49-
assignment !== null &&
50-
getClassDeclFromDecoratorProp(assignment) !== null
51-
);
98+
return ts.isStringLiteralLike(node) ? isAssignmentToPropertyWithName(node, 'template') : false;
5299
}
53100

54101
/**

vscode-ng-language-service/server/src/session.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import * as ts from 'typescript/lib/tsserverlibrary';
1616
import {promisify} from 'util';
1717
import {getLanguageService as getHTMLLanguageService} from 'vscode-html-languageservice';
18+
import {getSCSSLanguageService} from 'vscode-css-languageservice';
1819
import {TextDocument} from 'vscode-languageserver-textdocument';
1920
import * as lsp from 'vscode-languageserver/node';
2021

@@ -38,7 +39,7 @@ import {
3839

3940
import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
4041
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
41-
import {getHTMLVirtualContent} from './embedded_support';
42+
import {getHTMLVirtualContent, getSCSSVirtualContent} from './embedded_support';
4243
import {ServerHost} from './server_host';
4344
import {documentationToMarkdown} from './text_render';
4445
import {
@@ -84,6 +85,7 @@ const defaultFormatOptions: ts.FormatCodeSettings = {};
8485
let defaultPreferences: ts.UserPreferences = {};
8586

8687
const htmlLS = getHTMLLanguageService();
88+
const scssLS = getSCSSLanguageService();
8789

8890
const alwaysSuppressDiagnostics: number[] = [
8991
// Diagnostics codes whose errors should always be suppressed, regardless of the options
@@ -1059,7 +1061,18 @@ export class Session {
10591061
0,
10601062
virtualHtmlDocContents,
10611063
);
1062-
return [...htmlLS.getFoldingRanges(virtualHtmlDoc), ...angularFoldingRanges];
1064+
const virtualScssDocContents = getSCSSVirtualContent(sf);
1065+
const virtualScssDoc = TextDocument.create(
1066+
params.textDocument.uri.toString(),
1067+
'scss',
1068+
0,
1069+
virtualScssDocContents,
1070+
);
1071+
return [
1072+
...htmlLS.getFoldingRanges(virtualHtmlDoc),
1073+
...scssLS.getFoldingRanges(virtualScssDoc),
1074+
...angularFoldingRanges,
1075+
];
10631076
}
10641077

10651078
private onDefinition(

vscode-ng-language-service/server/src/tests/embedded_support_spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as ts from 'typescript';
1010

11-
import {getHTMLVirtualContent} from '../embedded_support';
11+
import {getHTMLVirtualContent, getSCSSVirtualContent} from '../embedded_support';
1212

1313
function assertEmbeddedHTMLContent(value: string, expectedContent: string): void {
1414
const sf = ts.createSourceFile('temp', value, ts.ScriptTarget.ESNext, true /* setParentNodes */);
@@ -17,6 +17,13 @@ function assertEmbeddedHTMLContent(value: string, expectedContent: string): void
1717
expect(virtualContent).toEqual(expectedContent);
1818
}
1919

20+
function assertEmbeddedSCSSContent(value: string, expectedContent: string): void {
21+
const sf = ts.createSourceFile('temp', value, ts.ScriptTarget.ESNext, true /* setParentNodes */);
22+
const virtualContent = getSCSSVirtualContent(sf);
23+
24+
expect(virtualContent).toEqual(expectedContent);
25+
}
26+
2027
describe('server embedded support', () => {
2128
it('strips everything but the template string literal', () => {
2229
assertEmbeddedHTMLContent(
@@ -41,4 +48,24 @@ describe('server embedded support', () => {
4148
// length and line break locations, our results will be just fine. It doesn't matter that we
4249
// have an extra whitespace character at the end of the line for folding ranges.
4350
});
51+
52+
describe('SCSS support', () => {
53+
it('strips everything but the styles string literal', () => {
54+
assertEmbeddedSCSSContent(
55+
`@Component({styles: ['abc123']}) export class MyCmp`,
56+
` abc123 `,
57+
);
58+
});
59+
60+
it('can locate multiple style literals', () => {
61+
assertEmbeddedSCSSContent(
62+
`@Component({styles: ['abc123', 'xyz789']})`,
63+
` abc123 xyz789 `,
64+
);
65+
});
66+
67+
it('can locate direct assignment to styles', () => {
68+
assertEmbeddedSCSSContent(`@Component({styles: 'abc123'})`, ` abc123 `);
69+
});
70+
});
4471
});

0 commit comments

Comments
 (0)