From 447bfa10bfbd3df56d879f6c016eb4cee7a910c6 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:52:44 -0400 Subject: [PATCH 1/9] fix(scss): resolve bare-module imports to node_modules on all platforms (#245579) --- src/services/cssNavigation.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/services/cssNavigation.ts b/src/services/cssNavigation.ts index 4192dfd2..b86ca0e5 100644 --- a/src/services/cssNavigation.ts +++ b/src/services/cssNavigation.ts @@ -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';" } } } @@ -422,7 +422,20 @@ 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 isBareImport = !target.startsWith('.') // not ./ or ../ + && !target.startsWith('/') // not workspace-absolute + && target.indexOf(':') === -1; // not a scheme (file://) + + 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) @@ -589,4 +602,4 @@ export function getModuleNameFromPath(path: string) { } // Otherwise get until first instance of '/' return path.substring(0, firstSlash); -} \ No newline at end of file +} From e4276938d318baffd0d7facd583fc802d7193d1c Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:52:58 -0400 Subject: [PATCH 2/9] test fix(scss): resolve bare-module imports to node_modules on all platforms (#245579) --- .../scss/scssNavigation-node-modules.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/scss/scssNavigation-node-modules.test.ts diff --git a/src/test/scss/scssNavigation-node-modules.test.ts b/src/test/scss/scssNavigation-node-modules.test.ts new file mode 100644 index 00000000..b21757bf --- /dev/null +++ b/src/test/scss/scssNavigation-node-modules.test.ts @@ -0,0 +1,17 @@ +import { assert } from 'chai'; +import { getSCSSLanguageService } from '../../cssLanguageService'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { FileType, getNodeFSRequestService } from '../../nodeFs'; + +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 target = links[0].target!.replace(/\\/g, '/'); + assert.match(target, /node_modules\/bootstrap\/scss\/_variables\.scss$/); + }); +}); From 0df65eb5f999d365880da4cbf701f8bfbe239971 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:02:15 -0400 Subject: [PATCH 3/9] Create .travis.yml --- .travis.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d3088c0c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +# Continuous Integration for vscode-css-languageservice +# Runs the TypeScript build & test suite on Node.js LTS versions + +language: node_js + +# Test against active LTS releases (adjust as project evolves) +node_js: + - "20" + - "18" + +# Speed up by re‑using npm cache across builds +cache: + npm: true + directories: + - ~/.npm + +install: + # Guaranteed clean, reproducible install + - npm ci + +script: + # Compile the TypeScript (equivalent to `gulp compile` in the repo scripts) + - npm run compile + # Run the unit test suite (e.g. Jest / Mocha configured in package.json) + - npm test + +# Only build push & PR branches; ignore tags unless they come from the repo owner +branches: + only: + - /.*/ From 74253090f3a4d17628bf6ed847039224e5c530c5 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:11:38 -0400 Subject: [PATCH 4/9] Create triggerbuild.txt --- triggerbuild.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 triggerbuild.txt diff --git a/triggerbuild.txt b/triggerbuild.txt new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/triggerbuild.txt @@ -0,0 +1 @@ +build From a562b114864d69334097b67126e547c2fec8f363 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:47:01 -0400 Subject: [PATCH 5/9] Delete .travis.yml --- .travis.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d3088c0c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Continuous Integration for vscode-css-languageservice -# Runs the TypeScript build & test suite on Node.js LTS versions - -language: node_js - -# Test against active LTS releases (adjust as project evolves) -node_js: - - "20" - - "18" - -# Speed up by re‑using npm cache across builds -cache: - npm: true - directories: - - ~/.npm - -install: - # Guaranteed clean, reproducible install - - npm ci - -script: - # Compile the TypeScript (equivalent to `gulp compile` in the repo scripts) - - npm run compile - # Run the unit test suite (e.g. Jest / Mocha configured in package.json) - - npm test - -# Only build push & PR branches; ignore tags unless they come from the repo owner -branches: - only: - - /.*/ From 5f2bc5a03bddfb1be23e84478fa27e99031bb625 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:02:24 -0400 Subject: [PATCH 6/9] Change target (URI) to not contain '\' character --- src/test/scss/scssNavigation-node-modules.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/scss/scssNavigation-node-modules.test.ts b/src/test/scss/scssNavigation-node-modules.test.ts index b21757bf..8bec199a 100644 --- a/src/test/scss/scssNavigation-node-modules.test.ts +++ b/src/test/scss/scssNavigation-node-modules.test.ts @@ -2,6 +2,7 @@ import { assert } from 'chai'; 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(); @@ -11,7 +12,7 @@ describe('SCSS link navigation – node_modules', () => { 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 target = links[0].target!.replace(/\\/g, '/'); - assert.match(target, /node_modules\/bootstrap\/scss\/_variables\.scss$/); + const expected = URI.file('c:/proj/node_modules/bootstrap/scss/_variables.scss').toString(); + assert.strictEqual(links[0].target, expected); }); }); From 2e618bec8587364b7932c7cb3d74e415516e4347 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Sat, 3 May 2025 14:34:58 -0400 Subject: [PATCH 7/9] Delete triggerbuild.txt --- triggerbuild.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 triggerbuild.txt diff --git a/triggerbuild.txt b/triggerbuild.txt deleted file mode 100644 index 378eac25..00000000 --- a/triggerbuild.txt +++ /dev/null @@ -1 +0,0 @@ -build From 1d6f46264d1723217e18f1b139ad5adbaa480369 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Sat, 3 May 2025 14:41:41 -0400 Subject: [PATCH 8/9] =?UTF-8?q?fix=20cssNavigation:=20detect=20scheme?= =?UTF-8?q?=E2=80=91prefixed=20import=20paths=20with=20regex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/cssNavigation.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/cssNavigation.ts b/src/services/cssNavigation.ts index b86ca0e5..0a74a75d 100644 --- a/src/services/cssNavigation.ts +++ b/src/services/cssNavigation.ts @@ -423,9 +423,11 @@ export class CSSNavigation { } // Treat bare module names (“bootstrap/...”) like sass-loader does - const isBareImport = !target.startsWith('.') // not ./ or ../ - && !target.startsWith('/') // not workspace-absolute - && target.indexOf(':') === -1; // not a scheme (file://) + const startsWithSchemeRegex = /^\w[\w\d+.-]:/; + + 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( From a75501c616401809399903b8ee08aa27e7ad85e6 Mon Sep 17 00:00:00 2001 From: Isaac Tian <87505494+IsaacTian05@users.noreply.github.com> Date: Sat, 3 May 2025 14:54:55 -0400 Subject: [PATCH 9/9] =?UTF-8?q?test=20scss=E2=80=91navigation=20:=20verify?= =?UTF-8?q?=20scheme=E2=80=91prefixed=20@import=20paths=20remain=20absolut?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `schemeImportLinks.test.ts` with four mocha cases covering `http://`, `https://`, `file://`, and `vscode-resource://` URLs --- src/test/scss/schemeImportLinks.test.ts | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/test/scss/schemeImportLinks.test.ts diff --git a/src/test/scss/schemeImportLinks.test.ts b/src/test/scss/schemeImportLinks.test.ts new file mode 100644 index 00000000..fe2b08fe --- /dev/null +++ b/src/test/scss/schemeImportLinks.test.ts @@ -0,0 +1,46 @@ +import * as assert from 'assert'; +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'); + }); +});