Skip to content

SCSS: resolve bare module imports on Windows (#245579) #440

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
23 changes: 19 additions & 4 deletions src/services/cssNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ export class CSSNavigation {
const documentFolderUri = dirname(documentUri);
const modulePath = await this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri);
if (modulePath) {
const pathWithinModule = ref.substring(moduleName.length + 1);
return joinPath(modulePath, pathWithinModule);
const remainder = ref.length > moduleName.length ? ref.slice(moduleName.length + 1) : ''; // skip the '/', bare import
return remainder ? joinPath(modulePath, remainder) : modulePath; // e.g. "@import 'bootstrap';"
}
}
}
Expand All @@ -422,7 +422,22 @@ export class CSSNavigation {
return this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink);
}

const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink);
// Treat bare module names (“bootstrap/...”) like sass-loader does
const startsWithSchemeRegex = /^\w[\w\d+.-]:/;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node module resolving should only happen when this.resolveModuleReferences is true.
(only set for LESS and SCSS).

So this needs to go behind the if (this.resolveModuleReferences) {.
I looks like we already call resolveModuleReference there, so this code is not necessary?


const isBareImport = !target.startsWith('.') // not ./ or ../
&& !target.startsWith('/') // not workspace-absolute
&& !startsWithSchemeRegex.test(target); // not a scheme (file://, http://, etc.)

if (isBareImport) {
const moduleRef = await this.mapReference(
await this.resolveModuleReference(target, documentUri, documentContext),
isRawLink);
if (moduleRef) { return moduleRef; }
}

const ref = await this.mapReference(
documentContext.resolveReference(target, documentUri), isRawLink);

// Following [less-loader](https://github.com/webpack-contrib/less-loader#imports)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#resolving-import-at-rules)
Expand Down Expand Up @@ -589,4 +604,4 @@ export function getModuleNameFromPath(path: string) {
}
// Otherwise get until first instance of '/'
return path.substring(0, firstSlash);
}
}
46 changes: 46 additions & 0 deletions src/test/scss/schemeImportLinks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as assert from 'assert';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move these into scssNavigation.test.ts, where we have all other tests for node module resolving.
Please use the already existing test framework and style (mocha with suite and test

import { getSCSSLanguageService } from '../../scssLanguageService';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { DocumentContext } from '../../cssLanguageTypes';

function createDocument(contents: string, uri = 'file:///test.scss') {
return TextDocument.create(uri, 'scss', 0, contents);
}

const dummyContext: DocumentContext = {
resolveReference: (ref: string, _base: string) => ref
};

const ls = getSCSSLanguageService();

async function getLinks(contents: string) {
const doc = createDocument(contents);
const stylesheet = ls.parseStylesheet(doc);
return ls.findDocumentLinks2(doc, stylesheet, dummyContext);
}

describe('SCSS Navigation – scheme URL imports', () => {
it('http scheme import is treated as absolute URL, not bare import', async () => {
const links = await getLinks(`@import "http://example.com/foo.css";`);
assert.strictEqual(links.length, 1);
assert.strictEqual(links[0].target, 'http://example.com/foo.css');
});

it('https scheme import is treated as absolute URL, not bare import', async () => {
const links = await getLinks(`@import "https://cdn.example.com/reset.css";`);
assert.strictEqual(links.length, 1);
assert.strictEqual(links[0].target, 'https://cdn.example.com/reset.css');
});

it('file scheme import is treated as absolute URL, not bare import', async () => {
const links = await getLinks(`@import "file:///Users/test/project/styles/base.scss";`);
assert.strictEqual(links.length, 1);
assert.strictEqual(links[0].target, 'file:///Users/test/project/styles/base.scss');
});

it('custom scheme import (vscode-resource) is treated as absolute URL, not bare import', async () => {
const links = await getLinks(`@import "vscode-resource://file/some.css";`);
assert.strictEqual(links.length, 1);
assert.strictEqual(links[0].target, 'vscode-resource://file/some.css');
});
});
18 changes: 18 additions & 0 deletions src/test/scss/scssNavigation-node-modules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { assert } from 'chai';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move these into scssNavigation.test.ts, where we have all other tests for node module resolving.
Please use the already existing test framework and style (mocha with suite and test

import { getSCSSLanguageService } from '../../cssLanguageService';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { FileType, getNodeFSRequestService } from '../../nodeFs';
import { URI } from 'vscode-uri';

const ls = getSCSSLanguageService();
const mockFS = getNodeFSRequestService();

describe('SCSS link navigation – node_modules', () => {
it('resolves bootstrap path on Windows', async () => {
const doc = TextDocument.create('file:///c:/proj/app.scss', 'scss', 1,
"@import 'bootstrap/scss/variables';");
const links = await ls.findDocumentLinks2(doc, ls.parseStylesheet(doc), {}, mockFS);
const expected = URI.file('c:/proj/node_modules/bootstrap/scss/_variables.scss').toString();
assert.strictEqual(links[0].target, expected);
});
});