From a6d35c652f2eb55ce51bf5c9dc81ba99f4dccdeb Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 24 Apr 2024 18:48:06 +0200 Subject: [PATCH] feat: add support for pkg links --- .gitignore | 1 + .../src/utils/fs-provider.ts | 3 + .../src/cssLanguageTypes.ts | 1 + .../src/services/cssNavigation.ts | 111 +++++++++ .../src/test/scss/scssNavigation.test.ts | 201 +++++++++++++++ .../src/test/testUtil/fsProvider.ts | 17 +- .../node_modules/@foo/baz/package.json | 13 + .../node_modules/bar-pattern/package.json | 13 + .../bar-pattern/styles/button.scss | 0 .../bar-pattern/styles/colors.scss | 0 .../bar-pattern/styles/index.scss | 0 .../bar-pattern/styles/theme/button.scss | 0 .../bar-pattern/styles/theme/colors.scss | 0 .../bar-pattern/styles/theme/index.scss | 0 .../node_modules/bar/package.json | 13 + .../node_modules/bar/styles/button.scss | 0 .../node_modules/bar/styles/colors.scss | 0 .../node_modules/bar/styles/index.scss | 0 .../node_modules/root-sass/package.json | 3 + .../node_modules/root-sass/styles/index.scss | 0 .../node_modules/root-style/package.json | 3 + .../node_modules/root-style/styles/index.scss | 0 .../e2e/suite/definition/definitions.test.js | 20 ++ .../test/e2e/suite/hover/hover.test.js | 9 + .../test/fixtures/pkg-import/.gitignore | 4 + .../test/fixtures/pkg-import/README.md | 3 + .../@my-scope/my-components/index.js | 1 + .../@my-scope/my-components/package.json | 15 ++ .../my-components/styles/colors.scss | 3 + .../@my-scope/my-components/styles/index.scss | 1 + .../node_modules/my-components/index.js | 1 + .../node_modules/my-components/package.json | 15 ++ .../my-components/styles/colors.scss | 3 + .../my-components/styles/index.scss | 1 + .../fixtures/pkg-import/package-lock.json | 230 ++++++++++++++++++ .../test/fixtures/pkg-import/package.json | 13 + .../test/fixtures/pkg-import/sass.test.js | 20 ++ .../test/fixtures/pkg-import/src/scoped.scss | 5 + .../test/fixtures/pkg-import/src/styles.scss | 5 + 39 files changed, 727 insertions(+), 1 deletion(-) create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/baz/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/button.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/colors.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/index.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/button.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/colors.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/index.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/button.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/colors.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/index.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/styles/index.scss create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/styles/index.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/.gitignore create mode 100644 vscode-extension/test/fixtures/pkg-import/README.md create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/index.js create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/package.json create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/colors.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/index.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/my-components/index.js create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/my-components/package.json create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/colors.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/index.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/package-lock.json create mode 100644 vscode-extension/test/fixtures/pkg-import/package.json create mode 100644 vscode-extension/test/fixtures/pkg-import/sass.test.js create mode 100644 vscode-extension/test/fixtures/pkg-import/src/scoped.scss create mode 100644 vscode-extension/test/fixtures/pkg-import/src/styles.scss diff --git a/.gitignore b/.gitignore index 1c2023a8..bd46a3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ npm-debug.log* node_modules/ !vscode-extension/test/fixtures/node_modules !vscode-extension/test/fixtures/completion/node_modules +!vscode-extension/test/fixtures/pkg-import/node_modules # Compiled and temporary files dist/ diff --git a/packages/language-services/src/utils/fs-provider.ts b/packages/language-services/src/utils/fs-provider.ts index ff70e173..40149bc4 100644 --- a/packages/language-services/src/utils/fs-provider.ts +++ b/packages/language-services/src/utils/fs-provider.ts @@ -29,6 +29,9 @@ export function mapFsProviders( ]); return result; }, + getContent(uri, encoding) { + return ours.readFile(URI.parse(uri), encoding); + }, }; return theirs; } diff --git a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts index b27973a9..217466f6 100644 --- a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts +++ b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts @@ -373,6 +373,7 @@ export interface FileStat { export interface FileSystemProvider { stat(uri: DocumentUri): Promise; readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>; + getContent?(uri: DocumentUri, encoding?: BufferEncoding): Promise; } export interface CSSFormatConfiguration { diff --git a/packages/vscode-css-languageservice/src/services/cssNavigation.ts b/packages/vscode-css-languageservice/src/services/cssNavigation.ts index 9cafc27b..4c0760d7 100644 --- a/packages/vscode-css-languageservice/src/services/cssNavigation.ts +++ b/packages/vscode-css-languageservice/src/services/cssNavigation.ts @@ -549,6 +549,13 @@ export class CSSNavigation { return this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink); } + // Following the [sass package importer](https://github.com/sass/sass/blob/f6832f974c61e35c42ff08b3640ff155071a02dd/js-api-doc/importer.d.ts#L349), + // look for the `exports` field of the module and any `sass`, `style` or `default` that matches the import. + // If it's only `pkg:module`, also look for `sass` and `style` on the root of package.json. + if (target.startsWith("pkg:")) { + return this.resolvePkgModulePath(target, documentUri, documentContext); + } + const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink); // Following [less-loader](https://github.com/webpack-contrib/less-loader#imports) @@ -612,6 +619,99 @@ export class CSSNavigation { return undefined; } + private async resolvePkgModulePath( + target: string, + documentUri: string, + documentContext: DocumentContext, + ): Promise { + const bareTarget = target.replace("pkg:", ""); + const moduleName = bareTarget.includes("/") ? getModuleNameFromPath(bareTarget) : bareTarget; + const rootFolderUri = documentContext.resolveReference("/", documentUri); + const documentFolderUri = dirname(documentUri); + const modulePath = await this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri); + if (modulePath) { + const packageJsonPath = `${modulePath}/package.json`; + if (packageJsonPath) { + // Since submodule exports import strings don't match the file system, + // we need the contents of `package.json` to look up the correct path. + let packageJsonContent = await this.getContent(packageJsonPath); + if (packageJsonContent) { + const packageJson: { + style?: string; + sass?: string; + exports: Record>; + } = JSON.parse(packageJsonContent); + + const subpath = bareTarget.substring(moduleName.length + 1); + if (packageJson.exports) { + if (!subpath) { + // look for the default/index export + const entry = + // @ts-expect-error If ['.'] is a string this just produces undefined + packageJson.exports["."]["sass"] || + // @ts-expect-error If ['.'] is a string this just produces undefined + packageJson.exports["."]["style"] || + // @ts-expect-error If ['.'] is a string this just produces undefined + packageJson.exports["."]["default"]; + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` + if (entry && entry.endsWith(".scss")) { + const entryPath = joinPath(modulePath, entry); + return entryPath; + } + } else { + // The import string may be with or without .scss. + // Likewise the exports entry. Look up both paths. + // However, they need to be relative (start with ./). + const lookupSubpath = subpath.endsWith(".scss") ? `./${subpath.replace(".scss", "")}` : `./${subpath}`; + const lookupSubpathScss = subpath.endsWith(".scss") ? `./${subpath}` : `./${subpath}.scss`; + const subpathObject = packageJson.exports[lookupSubpathScss] || packageJson.exports[lookupSubpath]; + if (subpathObject) { + // @ts-expect-error If subpathObject is a string this just produces undefined + const entry = subpathObject["sass"] || subpathObject["styles"] || subpathObject["default"]; + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` + if (entry && entry.endsWith(".scss")) { + const entryPath = joinPath(modulePath, entry); + return entryPath; + } + } else { + // We have a subpath, but found no matches on direct lookup. + // It may be a [subpath pattern](https://nodejs.org/api/packages.html#subpath-patterns). + for (const [maybePattern, subpathObject] of Object.entries(packageJson.exports)) { + if (!maybePattern.includes("*")) { + continue; + } + // Patterns may also be without `.scss` on the left side, so compare without on both sides + const re = new RegExp(maybePattern.replace("./", "\\./").replace(".scss", "").replace("*", "(.+)")); + const match = re.exec(lookupSubpath); + if (match) { + // @ts-expect-error If subpathObject is a string this just produces undefined + const entry = subpathObject["sass"] || subpathObject["styles"] || subpathObject["default"]; + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` + if (entry && entry.endsWith(".scss")) { + // The right-hand side of a subpath pattern is also a pattern. + // Replace the pattern with the match from our regexp capture group above. + const expandedPattern = entry.replace("*", match[1]); + const entryPath = joinPath(modulePath, expandedPattern); + return entryPath; + } + } + } + } + } + } else if (!subpath && (packageJson.sass || packageJson.style)) { + // Fall back to a direct lookup on `sass` and `style` on package root + const entry = packageJson.sass || packageJson.style; + if (entry) { + const entryPath = joinPath(modulePath, entry); + return entryPath; + } + } + } + } + } + return undefined; + } + protected async fileExists(uri: string): Promise { if (!this.fileSystemProvider) { return false; @@ -627,6 +727,17 @@ export class CSSNavigation { return false; } } + + protected async getContent(uri: string): Promise { + if (!this.fileSystemProvider || !this.fileSystemProvider.getContent) { + return null; + } + try { + return await this.fileSystemProvider.getContent(uri); + } catch (err) { + return null; + } + } } function getColorInformation(node: nodes.Node, document: TextDocument): ColorInformation | null { diff --git a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts b/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts index 86c77bd5..f466ab87 100644 --- a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts @@ -519,6 +519,207 @@ suite("SCSS - Navigation", () => { workspaceFolder, ); }); + + test("SCSS node package resolving", async () => { + let ls = getSCSSLS(); + let testUri = getTestResource("about.scss"); + let workspaceFolder = getTestResource(""); + await assertLinks( + ls, + `@use "pkg:bar"`, + [ + { + namespace: "bar", + range: newRange(5, 14), + target: getTestResource("node_modules/bar/styles/index.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar/colors"`, + [ + { + namespace: "colors", + range: newRange(5, 21), + target: getTestResource("node_modules/bar/styles/colors.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar/colors.scss"`, + [ + { + namespace: "colors", + range: newRange(5, 26), + target: getTestResource("node_modules/bar/styles/colors.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/baz"`, + [ + { + namespace: "baz", + range: newRange(5, 19), + target: getTestResource("node_modules/@foo/baz/styles/index.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/baz/colors"`, + [ + { + namespace: "colors", + range: newRange(5, 26), + target: getTestResource("node_modules/@foo/baz/styles/colors.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/baz/colors.scss"`, + [ + { + namespace: "colors", + range: newRange(5, 31), + target: getTestResource("node_modules/@foo/baz/styles/colors.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/baz/button"`, + [ + { + namespace: "button", + range: newRange(5, 26), + target: getTestResource("node_modules/@foo/baz/styles/button.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/baz/button.scss"`, + [ + { + namespace: "button", + range: newRange(5, 31), + target: getTestResource("node_modules/@foo/baz/styles/button.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:root-sass"`, + [ + { + namespace: "root-sass", + range: newRange(5, 20), + target: getTestResource("node_modules/root-sass/styles/index.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:root-style"`, + [ + { + namespace: "root-style", + range: newRange(5, 21), + target: getTestResource("node_modules/root-style/styles/index.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern/anything"`, + [ + { + namespace: "anything", + range: newRange(5, 31), + target: getTestResource("node_modules/bar-pattern/styles/anything.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern/anything.scss"`, + [ + { + namespace: "anything", + range: newRange(5, 36), + target: getTestResource("node_modules/bar-pattern/styles/anything.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern/theme/dark.scss"`, + [ + { + namespace: "dark", + range: newRange(5, 38), + target: getTestResource("node_modules/bar-pattern/styles/theme/dark.scss"), + type: nodes.NodeType.Use, + }, + ], + "scss", + testUri, + workspaceFolder, + ); + }); }); suite("Symbols", () => { diff --git a/packages/vscode-css-languageservice/src/test/testUtil/fsProvider.ts b/packages/vscode-css-languageservice/src/test/testUtil/fsProvider.ts index eb973913..161cb3a3 100644 --- a/packages/vscode-css-languageservice/src/test/testUtil/fsProvider.ts +++ b/packages/vscode-css-languageservice/src/test/testUtil/fsProvider.ts @@ -5,7 +5,7 @@ import { FileSystemProvider, FileType } from "../../cssLanguageTypes"; import { URI } from "vscode-uri"; -import { stat as fsStat, readdir } from "fs"; +import { stat as fsStat, readFile, readdir } from "fs"; export function getFsProvider(): FileSystemProvider { return { @@ -75,5 +75,20 @@ export function getFsProvider(): FileSystemProvider { }); }); }, + getContent(locationString, encoding = "utf-8") { + return new Promise((c, e) => { + const location = URI.parse(locationString); + if (location.scheme !== "file") { + e(new Error("Protocol not supported: " + location.scheme)); + return; + } + readFile(location.fsPath, encoding, (err, data) => { + if (err) { + return e(err); + } + c(data); + }); + }); + }, }; } diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/baz/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/baz/package.json new file mode 100644 index 00000000..b4f30b11 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/baz/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.scss" + }, + "./colors.scss": { + "sass": "./styles/colors.scss" + }, + "./button": { + "sass": "./styles/button.scss" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/package.json new file mode 100644 index 00000000..876ae8b3 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.scss" + }, + "./*.scss": { + "sass": "./styles/*.scss" + }, + "./theme/*": { + "sass": "./styles/theme/*.scss" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/button.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/button.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/colors.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/colors.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/index.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/button.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/button.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/colors.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/colors.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/index.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern/styles/theme/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/package.json new file mode 100644 index 00000000..b4f30b11 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.scss" + }, + "./colors.scss": { + "sass": "./styles/colors.scss" + }, + "./button": { + "sass": "./styles/button.scss" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/button.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/button.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/colors.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/colors.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/index.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar/styles/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/package.json new file mode 100644 index 00000000..9451bbf8 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/package.json @@ -0,0 +1,3 @@ +{ + "sass": "./styles/index.scss" +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/styles/index.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-sass/styles/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/package.json new file mode 100644 index 00000000..80b361f2 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/package.json @@ -0,0 +1,3 @@ +{ + "style": "./styles/index.scss" +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/styles/index.scss b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style/styles/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/vscode-extension/test/e2e/suite/definition/definitions.test.js b/vscode-extension/test/e2e/suite/definition/definitions.test.js index 78c3972d..e4a2c37a 100644 --- a/vscode-extension/test/e2e/suite/definition/definitions.test.js +++ b/vscode-extension/test/e2e/suite/definition/definitions.test.js @@ -9,9 +9,13 @@ const { testDefinition } = require("./helper"); describe("SCSS Definition Test", function () { const docUri = getDocUri("definition/main.scss"); + const pkgImportUri = getDocUri("pkg-import/src/styles.scss"); + const scopedPkgImportUri = getDocUri("pkg-import/src/scoped.scss"); before(async () => { await showFile(docUri); + await showFile(pkgImportUri); + await showFile(scopedPkgImportUri); await sleepCI(); }); @@ -56,4 +60,20 @@ describe("SCSS Definition Test", function () { await testDefinition(docUri, position(16, 17), expectedLocation); }); + + it("finds symbol from pkg: import", async () => { + const expectedDocumentUri = getDocUri( + "pkg-import/node_modules/my-components/styles/colors.scss", + ); + const expectedLocation = sameLineLocation(expectedDocumentUri, 3, 1, 15); + await testDefinition(pkgImportUri, position(4, 19), expectedLocation); + }); + + it("finds symbol from scoped pkg: import", async () => { + const expectedDocumentUri = getDocUri( + "pkg-import/node_modules/@my-scope/my-components/styles/colors.scss", + ); + const expectedLocation = sameLineLocation(expectedDocumentUri, 3, 1, 15); + await testDefinition(scopedPkgImportUri, position(4, 19), expectedLocation); + }); }); diff --git a/vscode-extension/test/e2e/suite/hover/hover.test.js b/vscode-extension/test/e2e/suite/hover/hover.test.js index 89fe99fb..1e4c6ded 100644 --- a/vscode-extension/test/e2e/suite/hover/hover.test.js +++ b/vscode-extension/test/e2e/suite/hover/hover.test.js @@ -4,10 +4,12 @@ const { testHover } = require("./helper"); describe("SCSS Hover Test", function () { const docUri = getDocUri("hover/main.scss"); const collisionUri = getDocUri("hover/collision.scss"); + const pkgImportUri = getDocUri("pkg-import/src/styles.scss"); before(async () => { await showFile(docUri); await showFile(collisionUri); + await showFile(pkgImportUri); await sleepCI(); }); @@ -108,4 +110,11 @@ describe("SCSS Hover Test", function () { await testHover(collisionUri, position(5, 20), expectedContents); }); + + it("shows hover for symbols from pkg: import", async () => { + const expectedContents = { + contents: ["Primary brand color"], + }; + await testHover(pkgImportUri, position(4, 19), expectedContents); + }); }); diff --git a/vscode-extension/test/fixtures/pkg-import/.gitignore b/vscode-extension/test/fixtures/pkg-import/.gitignore new file mode 100644 index 00000000..9a51caed --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/* +!node_modules/my-components +!node_modules/@my-scope diff --git a/vscode-extension/test/fixtures/pkg-import/README.md b/vscode-extension/test/fixtures/pkg-import/README.md new file mode 100644 index 00000000..6039497f --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/README.md @@ -0,0 +1,3 @@ +# pkg-import + +https://sass-lang.com/blog/announcing-pkg-importers/ diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/index.js b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/index.js new file mode 100644 index 00000000..184dfcc9 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/index.js @@ -0,0 +1 @@ +console.log("Hello, World!"); diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/package.json b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/package.json new file mode 100644 index 00000000..7b879ee1 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/package.json @@ -0,0 +1,15 @@ +{ + "name": "@my-scope/my-components", + "version": "1.0.0", + "description": "", + "main": "index.js", + "exports": { + ".": { + "sass": "./styles/index.scss", + "default": "./index.js" + }, + "./*.scss": { + "sass": "./styles/*.scss" + } + } +} diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/colors.scss b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/colors.scss new file mode 100644 index 00000000..48b07207 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/colors.scss @@ -0,0 +1,3 @@ +/// Primary brand color +/// @type {Color} +$color-primary: green; diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/index.scss b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/index.scss new file mode 100644 index 00000000..8166996f --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/@my-scope/my-components/styles/index.scss @@ -0,0 +1 @@ +@forward "./colors.scss"; diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/index.js b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/index.js new file mode 100644 index 00000000..184dfcc9 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/index.js @@ -0,0 +1 @@ +console.log("Hello, World!"); diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/package.json b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/package.json new file mode 100644 index 00000000..ce8f4c74 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/package.json @@ -0,0 +1,15 @@ +{ + "name": "my-components", + "version": "1.0.0", + "description": "", + "main": "index.js", + "exports": { + ".": { + "sass": "./styles/index.scss", + "default": "./index.js" + }, + "./*.scss": { + "sass": "./styles/*.scss" + } + } +} diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/colors.scss b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/colors.scss new file mode 100644 index 00000000..48b07207 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/colors.scss @@ -0,0 +1,3 @@ +/// Primary brand color +/// @type {Color} +$color-primary: green; diff --git a/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/index.scss b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/index.scss new file mode 100644 index 00000000..8166996f --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/node_modules/my-components/styles/index.scss @@ -0,0 +1 @@ +@forward "./colors.scss"; diff --git a/vscode-extension/test/fixtures/pkg-import/package-lock.json b/vscode-extension/test/fixtures/pkg-import/package-lock.json new file mode 100644 index 00000000..71dffaf1 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/package-lock.json @@ -0,0 +1,230 @@ +{ + "name": "pkg-import", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pkg-import", + "version": "1.0.0", + "devDependencies": { + "sass": "^1.71.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/sass": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", + "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + } +} diff --git a/vscode-extension/test/fixtures/pkg-import/package.json b/vscode-extension/test/fixtures/pkg-import/package.json new file mode 100644 index 00000000..9dbf4fc2 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/package.json @@ -0,0 +1,13 @@ +{ + "name": "pkg-import", + "version": "1.0.0", + "private": true, + "type": "commonjs", + "description": "Test using the pgk import from a module", + "scripts": { + "test": "node --test sass.test.js" + }, + "devDependencies": { + "sass": "^1.71.0" + } +} diff --git a/vscode-extension/test/fixtures/pkg-import/sass.test.js b/vscode-extension/test/fixtures/pkg-import/sass.test.js new file mode 100644 index 00000000..6126faf2 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/sass.test.js @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const assert = require("node:assert"); +const { test } = require("node:test"); +const sass = require("sass"); + +test("noscope", () => { + const css = sass.compile("src/styles.scss", { + importers: [new sass.NodePackageImporter()], + }); + assert.ok(css); + assert.match(css.css, /color: green/); +}); + +test("scope", () => { + const css = sass.compile("src/scoped.scss", { + importers: [new sass.NodePackageImporter()], + }); + assert.ok(css); + assert.match(css.css, /color: green/); +}); diff --git a/vscode-extension/test/fixtures/pkg-import/src/scoped.scss b/vscode-extension/test/fixtures/pkg-import/src/scoped.scss new file mode 100644 index 00000000..2b244732 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/src/scoped.scss @@ -0,0 +1,5 @@ +@use "pkg:@my-scope/my-components/colors"; + +button { + color: colors.$color-primary; +} diff --git a/vscode-extension/test/fixtures/pkg-import/src/styles.scss b/vscode-extension/test/fixtures/pkg-import/src/styles.scss new file mode 100644 index 00000000..eb301d10 --- /dev/null +++ b/vscode-extension/test/fixtures/pkg-import/src/styles.scss @@ -0,0 +1,5 @@ +@use "pkg:my-components/colors"; + +button { + color: colors.$color-primary; +}