Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#163967 css alias #368

Merged
merged 15 commits into from
Nov 27, 2023
1 change: 1 addition & 0 deletions src/cssLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function createFacade(parser: Parser, completion: CSSCompletion, hover: CSSHover
validation.configure(settings);
completion.configure(settings?.completion);
hover.configure(settings?.hover);
navigation.configure(settings?.importAliases);
},
setDataProviders: cssDataManager.setDataProviders.bind(cssDataManager),
doValidation: validation.doValidation.bind(validation),
Expand Down
4 changes: 4 additions & 0 deletions src/cssLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ export interface LanguageSettings {
lint?: LintSettings;
completion?: CompletionSettings;
hover?: HoverSettings;
importAliases?: AliasSettings;
}

export interface AliasSettings {
[key: string]: string;
}

export interface HoverSettings {
documentation?: boolean;
Expand Down
33 changes: 29 additions & 4 deletions src/services/cssNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
'use strict';

import {
Color, ColorInformation, ColorPresentation, DocumentHighlight, DocumentHighlightKind, DocumentLink, Location,
AliasSettings, Color, ColorInformation, ColorPresentation, DocumentHighlight, DocumentHighlightKind, DocumentLink, Location,
Position, Range, SymbolInformation, SymbolKind, TextEdit, WorkspaceEdit, TextDocument, DocumentContext, FileSystemProvider, FileType, DocumentSymbol
} from '../cssLanguageTypes';
import * as l10n from '@vscode/l10n';
Expand All @@ -24,10 +24,15 @@ const startsWithSchemeRegex = /^\w+:\/\//;
const startsWithData = /^data:/;

export class CSSNavigation {
protected defaultSettings?: AliasSettings;

constructor(protected fileSystemProvider: FileSystemProvider | undefined, private readonly resolveModuleReferences: boolean) {
}

public configure(settings: AliasSettings | undefined) {
this.defaultSettings = settings;
}

public findDefinition(document: TextDocument, position: Position, stylesheet: nodes.Node): Location | null {

const symbols = new Symbols(stylesheet);
Expand Down Expand Up @@ -228,7 +233,7 @@ export class CSSNavigation {
if (!selectionRange || !containsRange(range, selectionRange)) {
selectionRange = Range.create(range.start, range.start);
}

const entry: DocumentSymbol = {
name: name || l10n.t('<undefined>'),
kind,
Expand Down Expand Up @@ -376,7 +381,7 @@ export class CSSNavigation {
return target;
}

protected async resolveReference(target: string, documentUri: string, documentContext: DocumentContext, isRawLink = false): Promise<string | undefined> {
protected async resolveReference(target: string, documentUri: string, documentContext: DocumentContext, isRawLink = false, settings = this.defaultSettings): Promise<string | undefined> {

// Following [css-loader](https://github.com/webpack-contrib/css-loader#url)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#imports)
Expand All @@ -403,6 +408,26 @@ export class CSSNavigation {
return moduleReference;
}
}

// Try resolving the reference from the language configuration alias settings
if (ref && !(await this.fileExists(ref))) {
const rootFolderUri = documentContext.resolveReference('/', documentUri);
if (settings && rootFolderUri) {
// Specific file reference
if (target in settings) {
return this.mapReference(joinPath(rootFolderUri, settings[target]), isRawLink);
}
// Reference folder
const firstSlash = target.indexOf('/');
const prefix = `${target.substring(0, firstSlash)}/`;
if (prefix in settings) {
const aliasPath = (settings[prefix]).slice(0, -1);
let newPath = joinPath(rootFolderUri, aliasPath);
return this.mapReference(newPath = joinPath(newPath, target.substring(prefix.length - 1)), isRawLink);
}
}
}

// fall back. it might not exists
return ref;
}
Expand Down Expand Up @@ -522,4 +547,4 @@ function getModuleNameFromPath(path: string) {
}
// Otherwise get until first instance of '/'
return path.substring(0, firstSlash);
}
}
21 changes: 20 additions & 1 deletion src/test/css/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { colorFrom256RGB, colorFromHSL, colorFromHWB } from '../../languageFacts

import {
TextDocument, DocumentHighlightKind, Range, Position, TextEdit, Color,
ColorInformation, DocumentLink, SymbolKind, SymbolInformation, Location, LanguageService, Stylesheet, getCSSLanguageService, DocumentSymbol,
ColorInformation, DocumentLink, SymbolKind, SymbolInformation, Location, LanguageService, Stylesheet, getCSSLanguageService, DocumentSymbol, LanguageSettings,
} from '../../cssLanguageService';

import { URI } from 'vscode-uri';
Expand Down Expand Up @@ -184,6 +184,15 @@ function getCSSLS() {
return getCSSLanguageService({ fileSystemProvider: getFsProvider() });
}

function aliasSettings(): LanguageSettings {
return {
"importAliases": {
"@SingleStylesheet": "/src/assets/styles.css",
"@AssetsDir/": "/src/assets/",
}
};
}

suite('CSS - Navigation', () => {

suite('Scope', () => {
Expand Down Expand Up @@ -364,6 +373,16 @@ suite('CSS - Navigation', () => {
]);
});

test('aliased @import links', async function () {
const settings = aliasSettings();
const ls = getCSSLS();
ls.configure(settings);

await assertLinks(ls, '@import "@SingleStylesheet"', [{ range: newRange(8, 27), target: "test://test/src/assets/styles.css"}]);

await assertLinks(ls, '@import "@AssetsDir/styles.css"', [{ range: newRange(8, 31), target: "test://test/src/assets/styles.css"}]);
});

test('links in rulesets', async () => {
const ls = getCSSLS();
await assertLinks(ls, `body { background-image: url(./foo.jpg)`, [
Expand Down
49 changes: 46 additions & 3 deletions src/test/scss/scssNavigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,33 @@

import * as nodes from '../../parser/cssNodes';
import { assertSymbolsInScope, assertScopesAndSymbols, assertHighlights, assertColorSymbols, assertLinks, newRange, getTestResource, assertDocumentSymbols } from '../css/navigation.test';
import { getSCSSLanguageService, DocumentLink, TextDocument, SymbolKind } from '../../cssLanguageService';
import { getSCSSLanguageService, DocumentLink, TextDocument, SymbolKind, LanguageSettings } from '../../cssLanguageService';
import * as assert from 'assert';
import * as path from 'path';
import { URI, Utils } from 'vscode-uri';
import { URI } from 'vscode-uri';
import { getFsProvider } from '../testUtil/fsProvider';
import { getDocumentContext } from '../testUtil/documentContext';

function getSCSSLS() {
return getSCSSLanguageService({ fileSystemProvider: getFsProvider() });
}

async function assertDynamicLinks(docUri: string, input: string, expected: DocumentLink[]) {
function aliasSettings(): LanguageSettings {
return {
"importAliases": {
"@SassStylesheet": "/src/assets/styles.scss",
"@NoUnderscoreDir/": "/noUnderscore/",
"@UnderscoreDir/": "/underscore/",
"@BothDir/": "/both/",
}
};
}

async function assertDynamicLinks(docUri: string, input: string, expected: DocumentLink[], settings?: LanguageSettings) {
const ls = getSCSSLS();
if (settings) {
ls.configure(settings);
}
const document = TextDocument.create(docUri, 'scss', 0, input);

const stylesheet = ls.parseStylesheet(document);
Expand Down Expand Up @@ -177,6 +191,35 @@ suite('SCSS - Navigation', () => {

});

test('SCSS aliased links', async function () {
const fixtureRoot = path.resolve(__dirname, '../../../../src/test/scss/linkFixture');
const getDocumentUri = (relativePath: string) => {
return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true);
};

const settings = aliasSettings();
const ls = getSCSSLS();
ls.configure(settings);

await assertLinks(ls, '@import "@SassStylesheet"', [{ range: newRange(8, 25), target: "test://test/src/assets/styles.scss"}]);

await assertDynamicLinks(getDocumentUri('./'), `@import '@NoUnderscoreDir/foo'`, [
{ range: newRange(8, 30), target: getDocumentUri('./noUnderscore/foo.scss') }
], settings);

await assertDynamicLinks(getDocumentUri('./'), `@import '@UnderscoreDir/foo'`, [
{ range: newRange(8, 28), target: getDocumentUri('./underscore/_foo.scss') }
], settings);

await assertDynamicLinks(getDocumentUri('./'), `@import '@BothDir/foo'`, [
{ range: newRange(8, 22), target: getDocumentUri('./both/foo.scss') }
], settings);

await assertDynamicLinks(getDocumentUri('./'), `@import '@BothDir/_foo'`, [
{ range: newRange(8, 23), target: getDocumentUri('./both/_foo.scss') }
], settings);
});

test('SCSS module file links', async () => {
const fixtureRoot = path.resolve(__dirname, '../../../../src/test/scss/linkFixture/module');
const getDocumentUri = (relativePath: string) => {
Expand Down
Loading