diff --git a/fixtures/e2e/namespace/_index.scss b/fixtures/e2e/namespace/_index.scss index 80b27d07..5572b22a 100644 --- a/fixtures/e2e/namespace/_index.scss +++ b/fixtures/e2e/namespace/_index.scss @@ -1,3 +1,4 @@ @forward "./functions" as fun-*; @forward "./mixins" as mix-* hide secret, other-secret; @forward "./variables" hide $secret; +@forward "./show" show $one; diff --git a/fixtures/e2e/namespace/_show.scss b/fixtures/e2e/namespace/_show.scss new file mode 100644 index 00000000..4682347a --- /dev/null +++ b/fixtures/e2e/namespace/_show.scss @@ -0,0 +1,2 @@ +$one: blue; +$two: red; diff --git a/fixtures/unit/completion/multi-level-hide/colors/_index.scss b/fixtures/unit/completion/multi-level-hide/colors/_index.scss new file mode 100644 index 00000000..24240d5b --- /dev/null +++ b/fixtures/unit/completion/multi-level-hide/colors/_index.scss @@ -0,0 +1 @@ +@forward "./base" hide $color-white; diff --git a/fixtures/unit/completion/multi-level-hide/colors/base/_base.scss b/fixtures/unit/completion/multi-level-hide/colors/base/_base.scss new file mode 100644 index 00000000..ca1785b5 --- /dev/null +++ b/fixtures/unit/completion/multi-level-hide/colors/base/_base.scss @@ -0,0 +1,3 @@ +$color-black: black; +$color-grey: grey; +$color-white: white; diff --git a/fixtures/unit/completion/multi-level-hide/colors/base/_index.scss b/fixtures/unit/completion/multi-level-hide/colors/base/_index.scss new file mode 100644 index 00000000..6b08d505 --- /dev/null +++ b/fixtures/unit/completion/multi-level-hide/colors/base/_index.scss @@ -0,0 +1 @@ +@forward "./base" hide $color-black; diff --git a/fixtures/unit/completion/multi-level-hide/styles.scss b/fixtures/unit/completion/multi-level-hide/styles.scss new file mode 100644 index 00000000..66ca69e6 --- /dev/null +++ b/fixtures/unit/completion/multi-level-hide/styles.scss @@ -0,0 +1,3 @@ +@use "./colors"; + +$text-color: colors.| diff --git a/fixtures/unit/completion/same-symbol-name-hide/colors/_index.scss b/fixtures/unit/completion/same-symbol-name-hide/colors/_index.scss new file mode 100644 index 00000000..88cf8e2e --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/colors/_index.scss @@ -0,0 +1,2 @@ +@forward "./branch-a" hide $color-white; +@forward "./branch-b"; diff --git a/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_base.scss b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_base.scss new file mode 100644 index 00000000..933650ff --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_base.scss @@ -0,0 +1 @@ +$color-white: white; diff --git a/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_index.scss b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_index.scss new file mode 100644 index 00000000..e1106df5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-a/_index.scss @@ -0,0 +1 @@ +@forward "./base"; diff --git a/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_base.scss b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_base.scss new file mode 100644 index 00000000..933650ff --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_base.scss @@ -0,0 +1 @@ +$color-white: white; diff --git a/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_index.scss b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_index.scss new file mode 100644 index 00000000..e1106df5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/colors/branch-b/_index.scss @@ -0,0 +1 @@ +@forward "./base"; diff --git a/fixtures/unit/completion/same-symbol-name-hide/styles.scss b/fixtures/unit/completion/same-symbol-name-hide/styles.scss new file mode 100644 index 00000000..66ca69e6 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-hide/styles.scss @@ -0,0 +1,3 @@ +@use "./colors"; + +$text-color: colors.| diff --git a/fixtures/unit/completion/same-symbol-name-show/colors/_index.scss b/fixtures/unit/completion/same-symbol-name-show/colors/_index.scss new file mode 100644 index 00000000..d0b8a13e --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/colors/_index.scss @@ -0,0 +1,2 @@ +@forward "./branch-a" show $color-black; +@forward "./branch-b"; diff --git a/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_base.scss b/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_base.scss new file mode 100644 index 00000000..ca1785b5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_base.scss @@ -0,0 +1,3 @@ +$color-black: black; +$color-grey: grey; +$color-white: white; diff --git a/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_index.scss b/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_index.scss new file mode 100644 index 00000000..e1106df5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/colors/branch-a/_index.scss @@ -0,0 +1 @@ +@forward "./base"; diff --git a/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_base.scss b/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_base.scss new file mode 100644 index 00000000..ca1785b5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_base.scss @@ -0,0 +1,3 @@ +$color-black: black; +$color-grey: grey; +$color-white: white; diff --git a/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_index.scss b/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_index.scss new file mode 100644 index 00000000..e1106df5 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/colors/branch-b/_index.scss @@ -0,0 +1 @@ +@forward "./base"; diff --git a/fixtures/unit/completion/same-symbol-name-show/styles.scss b/fixtures/unit/completion/same-symbol-name-show/styles.scss new file mode 100644 index 00000000..66ca69e6 --- /dev/null +++ b/fixtures/unit/completion/same-symbol-name-show/styles.scss @@ -0,0 +1,3 @@ +@use "./colors"; + +$text-color: colors.| diff --git a/package-lock.json b/package-lock.json index a8303e62..bb4a8ca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "some-sass", - "version": "2.14.2", + "version": "2.14.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "some-sass", - "version": "2.14.2", + "version": "2.14.3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2aea135f..3b5d56fa 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "some-sass", "displayName": "Some Sass: extended support for SCSS and SassDoc", "description": "Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", - "version": "2.14.2", + "version": "2.14.3", "publisher": "SomewhatStationery", "license": "MIT", "engines": { diff --git a/server/src/features/completion/completion.ts b/server/src/features/completion/completion.ts index dccdd644..99010b7b 100644 --- a/server/src/features/completion/completion.ts +++ b/server/src/features/completion/completion.ts @@ -240,6 +240,7 @@ function traverseTree( accumulator: Map, leaf: IScssDocument, hiddenSymbols: string[] = [], + shownSymbols: string[] = [], accumulatedPrefix = "", ) { if (accumulator.has(leaf.uri)) { @@ -263,6 +264,7 @@ function traverseTree( document, context, hiddenSymbols, + shownSymbols, accumulatedPrefix, ); completionItems = completionItems.concat(variables); @@ -274,6 +276,7 @@ function traverseTree( document, context, hiddenSymbols, + shownSymbols, accumulatedPrefix, ); completionItems = completionItems.concat(mixins); @@ -285,6 +288,7 @@ function traverseTree( document, context, hiddenSymbols, + shownSymbols, accumulatedPrefix, ); completionItems = completionItems.concat(functions); @@ -294,6 +298,7 @@ function traverseTree( const placeholders = createPlaceholderCompletionItems( scssDocument, hiddenSymbols, + shownSymbols, ); completionItems = completionItems.concat(placeholders); } @@ -314,11 +319,18 @@ function traverseTree( } let hidden = hiddenSymbols; + let shown = shownSymbols; if ( (child as ScssForward).hide && (child as ScssForward).hide.length > 0 ) { - hidden = hiddenSymbols.concat((child as ScssForward).hide); + hidden = hidden.concat((child as ScssForward).hide); + } + if ( + (child as ScssForward).show && + (child as ScssForward).show.length > 0 + ) { + shown = shown.concat((child as ScssForward).show); } let prefix = accumulatedPrefix; @@ -332,6 +344,7 @@ function traverseTree( accumulator, childDocument, hidden, + shown, prefix, ); } diff --git a/server/src/features/completion/function-completion.ts b/server/src/features/completion/function-completion.ts index e3f4ff44..2339f880 100644 --- a/server/src/features/completion/function-completion.ts +++ b/server/src/features/completion/function-completion.ts @@ -23,6 +23,7 @@ export function createFunctionCompletionItems( currentDocument: TextDocument, context: CompletionContext, hiddenSymbols: string[] = [], + shownSymbols: string[] = [], prefix = "", ): CompletionItem[] { const completions: CompletionItem[] = []; @@ -39,6 +40,10 @@ export function createFunctionCompletionItems( continue; } + if (shownSymbols.length > 0 && !shownSymbols.includes(func.name)) { + continue; + } + // Client needs the namespace as part of the text that is matched, // and inserted text needs to include the `.` which will otherwise // be replaced (except when we're embedded in Vue, Svelte or Astro). diff --git a/server/src/features/completion/mixin-completion.ts b/server/src/features/completion/mixin-completion.ts index d10bdac2..3b4b599c 100644 --- a/server/src/features/completion/mixin-completion.ts +++ b/server/src/features/completion/mixin-completion.ts @@ -24,6 +24,7 @@ export function createMixinCompletionItems( currentDocument: TextDocument, context: CompletionContext, hiddenSymbols: string[] = [], + shownSymbols: string[] = [], prefix = "", ): CompletionItem[] { const completions: CompletionItem[] = []; @@ -40,6 +41,10 @@ export function createMixinCompletionItems( continue; } + if (shownSymbols.length > 0 && !shownSymbols.includes(mixin.name)) { + continue; + } + const documentation = makeMixinDocumentation(mixin, scssDocument); // Client needs the namespace as part of the text that is matched, diff --git a/server/src/features/completion/placeholder-completion.ts b/server/src/features/completion/placeholder-completion.ts index cb8b9b86..a555c2c0 100644 --- a/server/src/features/completion/placeholder-completion.ts +++ b/server/src/features/completion/placeholder-completion.ts @@ -11,6 +11,7 @@ import { applySassDoc } from "../../utils/sassdoc"; export function createPlaceholderCompletionItems( scssDocument: IScssDocument, hiddenSymbols: string[] = [], + shownSymbols: string[] = [], ): CompletionItem[] { const completions: CompletionItem[] = []; @@ -19,6 +20,10 @@ export function createPlaceholderCompletionItems( continue; } + if (shownSymbols.length > 0 && !shownSymbols.includes(placeholder.name)) { + continue; + } + const label = placeholder.name; const filterText = placeholder.name.substring(1); diff --git a/server/src/features/completion/variable-completion.ts b/server/src/features/completion/variable-completion.ts index 96ee0040..ad79c80e 100644 --- a/server/src/features/completion/variable-completion.ts +++ b/server/src/features/completion/variable-completion.ts @@ -18,6 +18,7 @@ export function createVariableCompletionItems( currentDocument: TextDocument, context: CompletionContext, hiddenSymbols: string[] = [], + shownSymbols: string[] = [], prefix = "", ): CompletionItem[] { const completions: CompletionItem[] = []; @@ -65,6 +66,10 @@ export function createVariableCompletionItems( continue; } + if (shownSymbols.length > 0 && !shownSymbols.includes(variable.name)) { + continue; + } + if (isPrivate) { sortText = label.replace(/^$[_-]/, ""); } diff --git a/server/src/parser/parser.ts b/server/src/parser/parser.ts index 59452718..c75ec14a 100644 --- a/server/src/parser/parser.ts +++ b/server/src/parser/parser.ts @@ -22,7 +22,7 @@ import type { IScssSymbols } from "./scss-symbol"; export const reModuleAtRule = /@(?:use|forward|import)/; export const reUse = /@use ["'|](?.+)["'|](?: as (?\*|\w+))?;/; export const reForward = - /@forward ["'|](?.+)["'|](?: as (?\w+-)\*)?(?: hide (?.+))?;/; + /@forward ["'|](?.+)["'|](?: as (?\w+-)\*)?(?: hide (?.+))?(?: show (?.+))?;/; export const reImport = /@import ["'|](?.+)["'|]/; export const rePlaceholder = /^\s*%(?\w+)/; export const rePlaceholderUsage = /\s*@extend\s+(?%[\w\d-_]+)/; @@ -152,6 +152,9 @@ async function findDocumentSymbols( hide: matchForward.groups?.["hide"] ? matchForward.groups["hide"].split(",").map((s) => s.trim()) : [], + show: matchForward.groups?.["show"] + ? matchForward.groups["show"].split(",").map((s) => s.trim()) + : [], }); } diff --git a/server/src/parser/scss-symbol.ts b/server/src/parser/scss-symbol.ts index afbf9940..1689efd8 100644 --- a/server/src/parser/scss-symbol.ts +++ b/server/src/parser/scss-symbol.ts @@ -47,6 +47,7 @@ export interface ScssUse extends ScssLink { export interface ScssForward extends ScssLink { hide: string[]; + show: string[]; prefix?: string; } diff --git a/server/src/test/features/completion-visibility.spec.ts b/server/src/test/features/completion-visibility.spec.ts new file mode 100644 index 00000000..503b0b46 --- /dev/null +++ b/server/src/test/features/completion-visibility.spec.ts @@ -0,0 +1,89 @@ +import * as assert from "assert"; +import { changeConfiguration, useContext } from "../../context-provider"; +import { doCompletion } from "../../features/completion"; +import { NodeFileSystem } from "../../node-file-system"; +import { IScssDocument } from "../../parser"; +import ScannerService from "../../scanner"; +import { getUri } from "../fixture-helper"; +import * as helpers from "../helpers"; + +describe("Providers/Completion", () => { + beforeEach(async () => { + helpers.createTestContext(new NodeFileSystem()); + + const settings = helpers.makeSettings({ + suggestFromUseOnly: true, + }); + changeConfiguration(settings); + }); + + describe("Hide", () => { + it("supports multi-level hiding", async () => { + const workspaceUri = getUri("completion/multi-level-hide/"); + const docUri = getUri("completion/multi-level-hide/styles.scss"); + const scanner = new ScannerService(); + await scanner.scan([docUri], workspaceUri); + const { storage } = useContext(); + const stylesDoc = storage.get(docUri) as IScssDocument; + + const completions = await doCompletion( + stylesDoc, + stylesDoc.getText().indexOf("|"), + ); + + // $color-black and $color-white are hidden at different points + + assert.equal( + completions.items.length, + 1, + "Expected only one suggestion from the multi-level-hide fixture", + ); + assert.equal(completions.items[0].label, "$color-grey"); + }); + + it("doesn't hide symbol with same name in different part of dependency graph", async () => { + const workspaceUri = getUri("completion/same-symbol-name-hide/"); + const docUri = getUri("completion/same-symbol-name-hide/styles.scss"); + const scanner = new ScannerService(); + await scanner.scan([docUri], workspaceUri); + const { storage } = useContext(); + const stylesDoc = storage.get(docUri) as IScssDocument; + + const completions = await doCompletion( + stylesDoc, + stylesDoc.getText().indexOf("|"), + ); + + // $color-white is hidden in branch-a, but not in branch-b + assert.equal( + completions.items.length, + 1, + "Expected a suggestion from the same-symbol-name-hide fixture", + ); + assert.equal(completions.items[0].label, "$color-white"); + }); + }); + + describe("Show", () => { + it("doesn't show symbol with same name in different part of dependency graph", async () => { + const workspaceUri = getUri("completion/same-symbol-name-show/"); + const docUri = getUri("completion/same-symbol-name-show/styles.scss"); + const scanner = new ScannerService(); + await scanner.scan([docUri], workspaceUri); + const { storage } = useContext(); + const stylesDoc = storage.get(docUri) as IScssDocument; + + const completions = await doCompletion( + stylesDoc, + stylesDoc.getText().indexOf("|"), + ); + + // One branch only shows $color-black, but the other has three symbols including another $color-black + assert.equal( + completions.items.length, + 4, + "Expected four suggestions from the same-symbol-name-show fixture", + ); + }); + }); +}); diff --git a/server/src/test/scanner/scanner-helper.ts b/server/src/test/fixture-helper.ts similarity index 72% rename from server/src/test/scanner/scanner-helper.ts rename to server/src/test/fixture-helper.ts index 8269017b..d3d663f9 100644 --- a/server/src/test/scanner/scanner-helper.ts +++ b/server/src/test/fixture-helper.ts @@ -2,7 +2,7 @@ import * as path from "path"; import { URI } from "vscode-uri"; function getDocPath(p: string) { - return path.resolve(__dirname, "../../../../fixtures/unit", p); + return path.resolve(__dirname, "../../../fixtures/unit", p); } export function getUri(p: string) { diff --git a/server/src/test/scanner/scanner.spec.ts b/server/src/test/scanner/scanner.spec.ts index 1cbf612e..40e4fc31 100644 --- a/server/src/test/scanner/scanner.spec.ts +++ b/server/src/test/scanner/scanner.spec.ts @@ -3,8 +3,8 @@ import { isMatch } from "micromatch"; import { useContext } from "../../context-provider"; import { NodeFileSystem } from "../../node-file-system"; import ScannerService from "../../scanner"; +import { getUri } from "../fixture-helper"; import * as helpers from "../helpers"; -import { getUri } from "./scanner-helper"; describe("Services/Scanner", () => { beforeEach(() => {