From f25c5f32470e11e13cb46813fb719f460f1b068c Mon Sep 17 00:00:00 2001
From: Vijaya Krishna <8697234+Viijay-Kr@users.noreply.github.com>
Date: Tue, 5 Mar 2024 19:30:10 +0100
Subject: [PATCH] feat(CSS): rename selector across references (#137)
---
.changeset/afraid-lions-watch.md | 5 +
.../react-app/src/test/SyntaxHighlight.tsx | 1 +
.../VariousSelectors/VariousSelectors.tsx | 1 +
package-lock.json | 4 +-
package.json | 7 +
src/extension.ts | 10 +-
src/parser/utils.ts | 4 +
src/parser/v2/css.ts | 2 +
src/providers/css/CSSProvider.ts | 489 +++++++++++-------
src/providers/css/codelens.ts | 6 +-
src/providers/css/colors.ts | 6 +-
src/providers/css/completion.ts | 4 +-
src/providers/css/definition.ts | 4 +-
src/providers/css/references.ts | 4 +-
src/providers/css/rename-selector.ts | 63 +++
src/settings/index.ts | 7 +
src/test/suite/extension.test.ts | 163 +++---
17 files changed, 514 insertions(+), 266 deletions(-)
create mode 100644 .changeset/afraid-lions-watch.md
create mode 100644 src/providers/css/rename-selector.ts
diff --git a/.changeset/afraid-lions-watch.md b/.changeset/afraid-lions-watch.md
new file mode 100644
index 0000000..b6d3d94
--- /dev/null
+++ b/.changeset/afraid-lions-watch.md
@@ -0,0 +1,5 @@
+---
+"react-ts-css": patch
+---
+
+feat(CSS): rename selector across references
diff --git a/examples/react-app/src/test/SyntaxHighlight.tsx b/examples/react-app/src/test/SyntaxHighlight.tsx
index b3a9506..425e03a 100644
--- a/examples/react-app/src/test/SyntaxHighlight.tsx
+++ b/examples/react-app/src/test/SyntaxHighlight.tsx
@@ -9,5 +9,6 @@ export default function SyntaxHighlight() {
+
;
}
diff --git a/examples/react-app/src/test/VariousSelectors/VariousSelectors.tsx b/examples/react-app/src/test/VariousSelectors/VariousSelectors.tsx
index 25e2aee..d7fa69e 100644
--- a/examples/react-app/src/test/VariousSelectors/VariousSelectors.tsx
+++ b/examples/react-app/src/test/VariousSelectors/VariousSelectors.tsx
@@ -7,5 +7,6 @@ export const VariousSelector = () => {
+
;
};
diff --git a/package-lock.json b/package-lock.json
index fa832f0..e102204 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-ts-css",
- "version": "2.4.1",
+ "version": "2.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "react-ts-css",
- "version": "2.4.1",
+ "version": "2.6.0",
"license": "MIT",
"dependencies": {
"typescript-cleanup-definitions": "^1.1.0"
diff --git a/package.json b/package.json
index 379cc17..8993b96 100644
--- a/package.json
+++ b/package.json
@@ -142,6 +142,12 @@
"title": "Code Lens for selectors",
"default": false,
"description": "Codelenses to references of selectors"
+ },
+ "reactTsScss.renameSelector": {
+ "type": "boolean",
+ "title": "Rename Selector",
+ "default": true,
+ "description": "Rename selectors across multiple locations"
}
}
},
@@ -161,6 +167,7 @@
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "npm run compile-tests && npm run compile && npm run lint",
"lint": "eslint src --ext ts",
+ "lint:fix": "eslint src --fix --ext ts",
"test": "node ./out/test/runTest.js",
"publish:vscode": "vsce publish",
"publish:openvsx": "ovsx publish",
diff --git a/src/extension.ts b/src/extension.ts
index 19333bb..fe580a1 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,20 +7,21 @@ import {
languages,
extensions,
} from "vscode";
+import Settings, { EXT_NAME, getSettings } from "./settings";
+import Store from "./store/Store";
import { DefnitionProvider } from "./providers/ts/definitions";
import { HoverProvider } from "./providers/ts/hover";
import {
SelectorsCompletionProvider,
ImportCompletionProvider,
} from "./providers/ts/completion";
-import Settings, { EXT_NAME, getSettings } from "./settings";
-import Store from "./store/Store";
import { DiagnosticCodeAction } from "./providers/ts/code-actions";
import { CssDocumentColorProvider } from "./providers/css/colors";
import { CssVariablesCompletion } from "./providers/css/completion";
import { CssDefinitionProvider } from "./providers/css/definition";
import { ReferenceProvider } from "./providers/css/references";
import { ReferenceCodeLensProvider } from "./providers/css/codelens";
+import { RenameSelectorProvider } from "./providers/css/rename-selector";
const documentSelector = [
{ scheme: "file", language: "typescriptreact" },
@@ -150,6 +151,10 @@ export async function activate(context: ExtensionContext): Promise {
cssModulesDocumentSelector,
new ReferenceCodeLensProvider()
);
+ const _cssRenameSelectorProvider = languages.registerRenameProvider(
+ cssModulesDocumentSelector,
+ new RenameSelectorProvider()
+ );
context.subscriptions.push(_selectorsCompletionProvider);
context.subscriptions.push(_importsCompletionProvider);
@@ -161,6 +166,7 @@ export async function activate(context: ExtensionContext): Promise {
context.subscriptions.push(_cssDefinitionProvider);
context.subscriptions.push(_cssReferenceProvider);
context.subscriptions.push(_cssCodeLensProvider);
+ context.subscriptions.push(_cssRenameSelectorProvider);
} catch (e) {
console.error(e);
window.showWarningMessage(
diff --git a/src/parser/utils.ts b/src/parser/utils.ts
index 8172f43..fdf125b 100644
--- a/src/parser/utils.ts
+++ b/src/parser/utils.ts
@@ -88,3 +88,7 @@ export const isNormal = (selector: string) => {
export const isCombination = (selector: string) => {
return selector.indexOf(".") > -1;
};
+
+export const stripSelectHelpers = (str: string) => {
+ return str.replace(/(\&\.)|(\&\-)|(&\s.)|&|^\./gm, "");
+};
diff --git a/src/parser/v2/css.ts b/src/parser/v2/css.ts
index 6c23de1..8a91189 100644
--- a/src/parser/v2/css.ts
+++ b/src/parser/v2/css.ts
@@ -95,6 +95,7 @@ export type Selector = {
range: Range;
content: string;
selectionRange: Range;
+ rule: string;
};
export type Variable = {
@@ -133,6 +134,7 @@ export const getSelectors = (ast: Stylesheet, document: TextDocument) => {
range,
content: parentNode.getText(),
selectionRange,
+ rule: selectorNode.getText(),
});
};
diff --git a/src/providers/css/CSSProvider.ts b/src/providers/css/CSSProvider.ts
index adda525..c280a59 100644
--- a/src/providers/css/CSSProvider.ts
+++ b/src/providers/css/CSSProvider.ts
@@ -11,6 +11,7 @@ import {
LocationLink,
Location,
Uri,
+ WorkspaceEdit,
} from "vscode";
import {
createStyleSheet,
@@ -26,24 +27,25 @@ import Store from "../../store/Store";
import { Function, Node, NodeType } from "../../css-node.types";
import {
isColorString,
+ isSuffix,
rangeLooseEqual,
rangeStrictEqual,
+ stripSelectHelpers,
toColorCode,
- toVsCodePosition,
toVsCodeRange,
} from "../../parser/utils";
import { TextDocument as css_TextDocument } from "vscode-css-languageservice";
import { ProviderKind } from "../types";
-import {
- isIdentifier,
- isImportDeclaration,
- isImportDefaultSpecifier,
- isStringLiteral,
-} from "@babel/types";
+import { isIdentifier, isStringLiteral } from "@babel/types";
import path = require("path");
import { ReferenceCodeLens } from "./codelens";
import { readFile } from "fs/promises";
+type CSSProviderOptions = {
+ providerKind: ProviderKind;
+ position: Position;
+ document?: TextDocument;
+};
export class CSSProvider {
public providerKind: ProviderKind = ProviderKind.Invalid;
/** Current Active Position in the Document */
@@ -52,11 +54,7 @@ export class CSSProvider {
public document: TextDocument;
public static PEEK_REFERENCES_COMMAND = "peek_lens_references";
- public constructor(options: {
- providerKind: ProviderKind;
- position: Position;
- document?: TextDocument;
- }) {
+ public constructor(options: CSSProviderOptions) {
this.providerKind = options.providerKind;
this.position = options.position;
if (options.document) {
@@ -66,49 +64,6 @@ export class CSSProvider {
}
}
- public async getCssVariablesForCompletion() {
- const module = normalizePath(this.document.uri.fsPath);
- const variables: CssParserResult["variables"] = [];
- const thisDocPath = this.document.uri.fsPath;
- if (module.endsWith(".css")) {
- const cssModules = Array.from(Store.cssModules.keys()).filter((c) =>
- c.endsWith(".css")
- );
- await Promise.allSettled(
- cssModules.map(async (m) => {
- const css_parser_result = await parseCss(m);
- if (css_parser_result) {
- variables.push(...css_parser_result.variables);
- }
- })
- );
- }
- const completionList = new CompletionList();
- for (const {
- name,
- value,
- location: { uri },
- } of variables) {
- if (name && value) {
- if (uri.fsPath === thisDocPath) {
- continue;
- }
- const item = new CompletionItem(name, CompletionItemKind.Variable);
- const candidate = this.getNodeAtOffset();
- item.detail = value;
- item.insertText = candidate?.getText().includes("var")
- ? `${name}`
- : `var(${name})`;
- if (isColorString(value)) {
- item.kind = CompletionItemKind.Color;
- item.detail = toColorCode(value);
- }
- completionList.items.push(item);
- }
- }
- return completionList;
- }
-
public getNodeAtOffset(): Node | undefined {
const styleSheet = createStyleSheet(this.document);
const offset = this.document.offsetAt(this.position);
@@ -139,6 +94,200 @@ export class CSSProvider {
return;
}
+ public async getReferenceCandidates() {
+ const candidates: string[] = [];
+ const filePath = normalizePath(this.document.uri.fsPath);
+ try {
+ await Promise.allSettled(
+ Array.from(Store.tsModules.entries()).map(async ([k, v]) => {
+ if ([".tsx", ".jsx"].includes(path.extname(v))) {
+ const document = await readFile(v);
+ if (document) {
+ const text = document.toString();
+ const fileImportPattern = new RegExp(path.basename(filePath));
+ const fileImportMatches = text.match(fileImportPattern);
+ if (fileImportMatches) {
+ candidates.push(v);
+ }
+ }
+ }
+ })
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ return candidates;
+ }
+
+ public async getSelectorRange(): Promise {
+ const selectorAtRange = await this.getSelectorAtPosition();
+ return selectorAtRange ? toVsCodeRange(selectorAtRange.range) : undefined;
+ }
+
+ public async getSelectorAtPosition() {
+ const filePath = normalizePath(this.document.uri.fsPath);
+ const source_css_file = Store.cssModules.get(filePath);
+ const selectors = (await parseCss(source_css_file ?? ""))?.selectors;
+ const range = this.document.getWordRangeAtPosition(this.position);
+ let selectorAtRange: Selector | undefined;
+
+ if (selectors) {
+ for (const [, value] of selectors.entries()) {
+ if (range && value.range) {
+ if (rangeLooseEqual(range, value.range)) {
+ selectorAtRange = value;
+ }
+ }
+ }
+ }
+
+ return selectorAtRange;
+ }
+
+ public async getReferences() {
+ const referenceCandidates = await this.getReferenceCandidates();
+ const references = await Promise.allSettled(
+ referenceCandidates.map(async (c) => ({
+ uri: c,
+ parsed_result: await Store.parser?.getParsedResultByFile(c),
+ }))
+ ).catch((e) => {
+ throw e;
+ });
+ return references;
+ }
+}
+
+export class CSSCodeLensProvider extends CSSProvider {
+ constructor(options: CSSProviderOptions) {
+ super(options);
+ }
+
+ public async resolveCodeLens(range: Range): Promise {
+ let candidates: Location[] = [];
+
+ let selectorAtRange = await this.getSelectorAtPosition();
+ const references = await this.getReferences();
+
+ for (const ref of references) {
+ if (ref.status === "fulfilled") {
+ const parsedResult = ref.value.parsed_result?.parsedResult;
+ if (parsedResult) {
+ for (const accessor of parsedResult.style_accessors) {
+ let _selector;
+ if (isStringLiteral(accessor.property)) {
+ _selector = accessor.property.value;
+ } else if (isIdentifier(accessor.property)) {
+ _selector = accessor.property.name;
+ }
+ if (selectorAtRange?.selector === _selector) {
+ const preferedRange = (() => {
+ return new Range(
+ new Position(
+ accessor.property.loc!.start.line - 1,
+ accessor.property.loc!.start.column
+ ),
+ new Position(
+ accessor.property.loc!.end.line - 1,
+ accessor.property.loc!.end.column
+ )
+ );
+ })();
+ candidates.push(
+ new Location(Uri.file(ref.value.uri), preferedRange)
+ );
+ }
+ }
+ }
+ }
+ }
+ return candidates;
+ }
+
+ public async provideCodeLenses(): Promise {
+ const filePath = normalizePath(this.document.uri.fsPath);
+ const source_css_file = Store.cssModules.get(filePath);
+ const selectors = (await parseCss(source_css_file ?? ""))?.selectors;
+ const codeLens: ReferenceCodeLens[] = [];
+ if (selectors) {
+ for (const [, _selector] of selectors?.entries()) {
+ const range = toVsCodeRange(_selector.range);
+ codeLens.push(
+ new ReferenceCodeLens(this.document, this.document.fileName, range)
+ );
+ }
+ }
+ return codeLens;
+ }
+}
+
+export class CSSReferenceProvider extends CSSProvider {
+ constructor(options: CSSProviderOptions) {
+ super(options);
+ }
+
+ public async provideReferences(
+ onlySelectorRange?: boolean
+ ): Promise {
+ let candidates: Location[] = [];
+ let selectorAtRange = await this.getSelectorAtPosition();
+ const references = await this.getReferences();
+
+ for (const ref of references) {
+ if (ref.status === "fulfilled") {
+ const parsedResult = ref.value.parsed_result?.parsedResult;
+ if (parsedResult) {
+ for (const accessor of parsedResult.style_accessors) {
+ let _selector;
+ if (isStringLiteral(accessor.property)) {
+ _selector = accessor.property.value;
+ } else if (isIdentifier(accessor.property)) {
+ _selector = accessor.property.name;
+ }
+ if (selectorAtRange?.selector === _selector) {
+ const preferedRange = (() => {
+ if (onlySelectorRange) {
+ return new Range(
+ new Position(
+ accessor.property.loc!.start.line - 1,
+ accessor.property.loc!.start.column
+ ),
+ new Position(
+ accessor.property.loc!.end.line - 1,
+ accessor.property.loc!.end.column
+ )
+ );
+ }
+ return new Range(
+ new Position(
+ accessor.object.loc!.start.line - 1,
+ accessor.object.loc!.start.column
+ ),
+ new Position(
+ accessor.property.loc!.end.line - 1,
+ accessor.property.loc!.end.column
+ )
+ );
+ })();
+ candidates.push(
+ new Location(Uri.file(ref.value.uri), preferedRange)
+ );
+ }
+ }
+ }
+ }
+ }
+
+ return candidates;
+ }
+}
+
+export class CSSColorInfoProvider extends CSSProvider {
+ constructor(options: CSSProviderOptions) {
+ super(options);
+ }
+
public async provideColorInformation(): Promise {
const colorInformation: ColorInformation[] = [];
const colorVariables: Set = new Set();
@@ -205,7 +354,9 @@ export class CSSProvider {
// @ts-ignore
return ls.getColorPresentations(_document, stylesheet, color, range);
}
+}
+export class CSSDefinitionProvider extends CSSProvider {
public async provideDefinitions(): Promise {
const nodeAtOffset = this.getNodeAtOffset();
const candidates: LocationLink[] = [];
@@ -241,168 +392,128 @@ export class CSSProvider {
}
return candidates;
}
+}
- public async provideReferences(): Promise {
- const range = this.document.getWordRangeAtPosition(this.position);
- const referenceCandidates = await this.getReferenceCandidates();
- const candidates: Location[] = [];
- const filePath = normalizePath(this.document.uri.fsPath);
- const css_parser_result = await parseCss(filePath);
- const selectors = css_parser_result?.selectors;
- let selectorAtRange: Selector | undefined;
-
- if (selectors) {
- for (const [, value] of selectors.entries()) {
- if (range && value.range) {
- if (rangeLooseEqual(range, value.range)) {
- selectorAtRange = value;
+export class CSSVariableCompletionProvider extends CSSProvider {
+ constructor(options: CSSProviderOptions) {
+ super(options);
+ }
+ public async getCssVariablesForCompletion() {
+ const module = normalizePath(this.document.uri.fsPath);
+ const variables: CssParserResult["variables"] = [];
+ const thisDocPath = this.document.uri.fsPath;
+ if (module.endsWith(".css")) {
+ const cssModules = Array.from(Store.cssModules.keys()).filter((c) =>
+ c.endsWith(".css")
+ );
+ await Promise.allSettled(
+ cssModules.map(async (m) => {
+ const css_parser_result = await parseCss(m);
+ if (css_parser_result) {
+ variables.push(...css_parser_result.variables);
}
- }
- }
- }
- if (!selectorAtRange) {
- return [];
+ })
+ );
}
- const references = await Promise.allSettled(
- referenceCandidates.map(async (c) => ({
- uri: c,
- parsed_result: await Store.parser?.getParsedResultByFile(c),
- }))
- );
-
- for (const ref of references) {
- if (ref.status === "fulfilled") {
- const parsedResult = ref.value.parsed_result?.parsedResult;
- if (parsedResult) {
- for (const accessor of parsedResult.style_accessors) {
- let _selector;
- if (isStringLiteral(accessor.property)) {
- _selector = accessor.property.value;
- } else if (isIdentifier(accessor.property)) {
- _selector = accessor.property.name;
- }
- if (selectorAtRange.selector === _selector) {
- const preferedRange = (() => {
- return new Range(
- new Position(
- accessor.object.loc!.start.line - 1,
- accessor.object.loc!.start.column
- ),
- new Position(
- accessor.property.loc!.end.line - 1,
- accessor.property.loc!.end.column
- )
- );
- })();
- candidates.push(
- new Location(Uri.file(ref.value.uri), preferedRange)
- );
- }
- }
+ const completionList = new CompletionList();
+ for (const {
+ name,
+ value,
+ location: { uri },
+ } of variables) {
+ if (name && value) {
+ if (uri.fsPath === thisDocPath) {
+ continue;
+ }
+ const item = new CompletionItem(name, CompletionItemKind.Variable);
+ const candidate = this.getNodeAtOffset();
+ item.detail = value;
+ item.insertText = candidate?.getText().includes("var")
+ ? `${name}`
+ : `var(${name})`;
+ if (isColorString(value)) {
+ item.kind = CompletionItemKind.Color;
+ item.detail = toColorCode(value);
}
+ completionList.items.push(item);
}
}
-
- return candidates;
+ return completionList;
}
+}
- public async resolveCodeLens(range: Range): Promise {
- let candidates: Location[] = [];
- const filePath = normalizePath(this.document.uri.fsPath);
- const css_parser_result = await parseCss(filePath);
- const selectors = css_parser_result?.selectors;
- const referenceCandidates = await this.getReferenceCandidates();
- let selectorAtRange: Selector | undefined;
- console.log(range, this.document.getText(range));
- if (selectors) {
- for (const [, value] of selectors.entries()) {
- if (range && value.range) {
- if (rangeLooseEqual(range, value.range)) {
- selectorAtRange = value;
- }
- }
- }
- }
+export class CSSRenameProvider extends CSSProvider {
+ constructor(options: CSSProviderOptions) {
+ super(options);
+ }
- const references = await Promise.allSettled(
- referenceCandidates.map(async (c) => ({
- uri: c,
- parsed_result: await Store.parser?.getParsedResultByFile(c),
- }))
- );
+ public async provideRenameReferences(
+ newName: string
+ ): Promise> {
+ const candidates: Array = [];
+ let selectorAtPosition = await this.getSelectorAtPosition();
+ let range = await this.getSelectorRange();
+ const references = await this.getReferences();
for (const ref of references) {
if (ref.status === "fulfilled") {
const parsedResult = ref.value.parsed_result?.parsedResult;
if (parsedResult) {
for (const accessor of parsedResult.style_accessors) {
let _selector;
+ let selectorType: "Literal" | "Identifier" | undefined;
if (isStringLiteral(accessor.property)) {
_selector = accessor.property.value;
+ selectorType = "Literal";
} else if (isIdentifier(accessor.property)) {
_selector = accessor.property.name;
+ selectorType = "Identifier";
}
- if (selectorAtRange?.selector === _selector) {
+ if (selectorAtPosition?.selector === _selector) {
const preferedRange = (() => {
- return new Range(
- new Position(
- accessor.property.loc!.start.line - 1,
- accessor.property.loc!.start.column
- ),
- new Position(
- accessor.property.loc!.end.line - 1,
- accessor.property.loc!.end.column
- )
- );
+ if (selectorType === "Literal") {
+ return new Range(
+ new Position(
+ accessor.property.loc!.start.line - 1,
+ accessor.property.loc!.start.column + 1
+ ),
+ new Position(
+ accessor.property.loc!.end.line - 1,
+ accessor.property.loc!.end.column - 1
+ )
+ );
+ } else if (selectorType === "Identifier") {
+ return new Range(
+ new Position(
+ accessor.property.loc!.start.line - 1,
+ accessor.property.loc!.start.column
+ ),
+ new Position(
+ accessor.property.loc!.end.line - 1,
+ accessor.property.loc!.end.column
+ )
+ );
+ }
})();
- candidates.push(
- new Location(Uri.file(ref.value.uri), preferedRange)
- );
+ if (preferedRange) {
+ let previous = stripSelectHelpers(this.document.getText(range));
+ let replacement = stripSelectHelpers(newName);
+ let loc = new Location(Uri.file(ref.value.uri), preferedRange);
+ candidates.push({
+ ...loc,
+ text: isSuffix(newName)
+ ? `${selectorAtPosition?.selector.replace(
+ previous,
+ ""
+ )}${replacement}`
+ : replacement,
+ });
+ }
}
}
}
}
}
- return candidates;
- }
-
- public async getReferenceCandidates() {
- const candidates: string[] = [];
- const filePath = normalizePath(this.document.uri.fsPath);
- try {
- await Promise.allSettled(
- Array.from(Store.tsModules.entries()).map(async ([k, v]) => {
- if ([".tsx", ".jsx"].includes(path.extname(v))) {
- const document = await readFile(v);
- if (document) {
- const text = document.toString();
- const fileImportPattern = new RegExp(path.basename(filePath));
- const fileImportMatches = text.match(fileImportPattern);
- if (fileImportMatches) {
- candidates.push(v);
- }
- }
- }
- })
- );
- } catch (e) {
- console.error(e);
- }
return candidates;
}
- public async provideCodeLenses(): Promise {
- const filePath = normalizePath(this.document.uri.fsPath);
- const source_css_file = Store.cssModules.get(filePath);
- const selectors = (await parseCss(source_css_file ?? ""))?.selectors;
- const codeLens: ReferenceCodeLens[] = [];
- if (selectors) {
- for (const [, _selector] of selectors?.entries()) {
- const range = toVsCodeRange(_selector.range);
- codeLens.push(
- new ReferenceCodeLens(this.document, this.document.fileName, range)
- );
- }
- }
- return codeLens;
- }
}
diff --git a/src/providers/css/codelens.ts b/src/providers/css/codelens.ts
index 0578115..5779664 100644
--- a/src/providers/css/codelens.ts
+++ b/src/providers/css/codelens.ts
@@ -1,7 +1,7 @@
import * as vscode from "vscode";
import Settings from "../../settings";
import { ProviderKind } from "../types";
-import { CSSProvider } from "./CSSProvider";
+import { CSSCodeLensProvider, CSSProvider } from "./CSSProvider";
export class ReferenceCodeLens extends vscode.CodeLens {
constructor(
@@ -22,7 +22,7 @@ export class ReferenceCodeLensProvider implements vscode.CodeLensProvider {
return [];
}
try {
- const provider = new CSSProvider({
+ const provider = new CSSCodeLensProvider({
providerKind: ProviderKind.CodeLens,
document,
position: new vscode.Position(0, 0),
@@ -45,7 +45,7 @@ export class ReferenceCodeLensProvider implements vscode.CodeLensProvider {
try {
const uri = codeLens.document.uri;
const position = codeLens.range.start;
- const provider = new CSSProvider({
+ const provider = new CSSCodeLensProvider({
providerKind: ProviderKind.CodeLens,
document: codeLens.document,
position: codeLens.range.start,
diff --git a/src/providers/css/colors.ts b/src/providers/css/colors.ts
index edfd18b..2e181ca 100644
--- a/src/providers/css/colors.ts
+++ b/src/providers/css/colors.ts
@@ -11,7 +11,7 @@ import {
} from "vscode";
import Settings from "../../settings";
import { ProviderKind } from "../types";
-import { CSSProvider } from "./CSSProvider";
+import { CSSColorInfoProvider, CSSProvider } from "./CSSProvider";
export class CssDocumentColorProvider implements _DocumentColorProvider {
async provideDocumentColors(
@@ -20,7 +20,7 @@ export class CssDocumentColorProvider implements _DocumentColorProvider {
if (!Settings.cssSyntaxColor) {
return [];
}
- const provider = new CSSProvider({
+ const provider = new CSSColorInfoProvider({
providerKind: ProviderKind.Colors,
document,
position: new Position(0, 0), // providing a dummy position as it not needed for document
@@ -35,7 +35,7 @@ export class CssDocumentColorProvider implements _DocumentColorProvider {
if (!Settings.cssSyntaxColor) {
return [];
}
- const provider = new CSSProvider({
+ const provider = new CSSColorInfoProvider({
providerKind: ProviderKind.Colors,
document: context.document,
position: new Position(0, 0), // providing a dummy position as it not needed for document
diff --git a/src/providers/css/completion.ts b/src/providers/css/completion.ts
index f7e1ae9..6c792b0 100644
--- a/src/providers/css/completion.ts
+++ b/src/providers/css/completion.ts
@@ -6,7 +6,7 @@ import {
} from "vscode";
import Settings from "../../settings";
import { ProviderKind } from "../types";
-import { CSSProvider } from "./CSSProvider";
+import { CSSProvider, CSSVariableCompletionProvider } from "./CSSProvider";
export class CssVariablesCompletion implements CompletionItemProvider {
async provideCompletionItems(
@@ -17,7 +17,7 @@ export class CssVariablesCompletion implements CompletionItemProvider {
if (!Settings.cssAutoComplete) {
return;
}
- const provider = new CSSProvider({
+ const provider = new CSSVariableCompletionProvider({
providerKind: ProviderKind.Completion,
position,
document,
diff --git a/src/providers/css/definition.ts b/src/providers/css/definition.ts
index 7b190fa..f1cd5c8 100644
--- a/src/providers/css/definition.ts
+++ b/src/providers/css/definition.ts
@@ -1,7 +1,7 @@
import * as vscode from "vscode";
import Settings from "../../settings";
import { ProviderKind } from "../types";
-import { CSSProvider } from "./CSSProvider";
+import { CSSDefinitionProvider, CSSProvider } from "./CSSProvider";
export class CssDefinitionProvider implements vscode.DefinitionProvider {
async provideDefinition(
@@ -11,7 +11,7 @@ export class CssDefinitionProvider implements vscode.DefinitionProvider {
if (!Settings.cssDefinitions) {
return [];
}
- const provider = new CSSProvider({
+ const provider = new CSSDefinitionProvider({
document,
position,
providerKind: ProviderKind.Definition,
diff --git a/src/providers/css/references.ts b/src/providers/css/references.ts
index d0aa33b..e6aaf6b 100644
--- a/src/providers/css/references.ts
+++ b/src/providers/css/references.ts
@@ -2,7 +2,7 @@ import * as vscode from "vscode";
import Settings from "../../settings";
import Store from "../../store/Store";
import { ProviderKind } from "../types";
-import { CSSProvider } from "./CSSProvider";
+import { CSSProvider, CSSReferenceProvider } from "./CSSProvider";
export class ReferenceProvider implements vscode.ReferenceProvider {
provideReferences(
@@ -13,7 +13,7 @@ export class ReferenceProvider implements vscode.ReferenceProvider {
return [];
}
try {
- const provider = new CSSProvider({
+ const provider = new CSSReferenceProvider({
document,
position,
providerKind: ProviderKind.References,
diff --git a/src/providers/css/rename-selector.ts b/src/providers/css/rename-selector.ts
new file mode 100644
index 0000000..0fb8819
--- /dev/null
+++ b/src/providers/css/rename-selector.ts
@@ -0,0 +1,63 @@
+import {
+ CancellationToken,
+ Position,
+ ProviderResult,
+ Range,
+ RenameProvider,
+ TextDocument,
+ TextEdit,
+ WorkspaceEdit,
+} from "vscode";
+import Settings from "../../settings";
+import { CSSProvider, CSSRenameProvider } from "./CSSProvider";
+import { ProviderKind } from "../types";
+import { isSuffix, stripSelectHelpers } from "../../parser/utils";
+export class RenameSelectorProvider implements RenameProvider {
+ async provideRenameEdits(
+ document: TextDocument,
+ position: Position,
+ newName: string,
+ token: CancellationToken
+ ): Promise {
+ if (!Settings.renameSelector || token.isCancellationRequested) {
+ return;
+ }
+ const provider = new CSSRenameProvider({
+ providerKind: ProviderKind.RenameSelector,
+ document,
+ position,
+ });
+ const edits = new WorkspaceEdit();
+
+ let range = await provider.getSelectorRange();
+ const locations = await provider.provideRenameReferences(newName);
+ if (range) {
+ edits.set(document.uri, [new TextEdit(range, newName)]);
+ }
+ for (const loc of locations) {
+ edits.set(loc.uri, [new TextEdit(loc.range, loc.text)]);
+ }
+ return edits;
+ }
+ async prepareRename?(
+ document: TextDocument,
+ position: Position,
+ token: CancellationToken
+ ): Promise {
+ if (!Settings.renameSelector || token.isCancellationRequested) {
+ return;
+ }
+ try {
+ const provider = new CSSRenameProvider({
+ providerKind: ProviderKind.RenameSelector,
+ document,
+ position,
+ });
+ const range = await provider.getSelectorRange();
+ return range;
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ }
+}
diff --git a/src/settings/index.ts b/src/settings/index.ts
index e48480f..b4c8a0c 100644
--- a/src/settings/index.ts
+++ b/src/settings/index.ts
@@ -110,6 +110,13 @@ export class Settings {
public set codeLens(v: Array | undefined) {
workspace.getConfiguration(EXT_NAME).update("codelens", v);
}
+ public get renameSelector(): Array | undefined {
+ return getSettings().get("renameSelector");
+ }
+
+ public set renameSelector(v: Array | undefined) {
+ workspace.getConfiguration(EXT_NAME).update("renameSelector", v);
+ }
}
export default new Settings();
diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts
index 5206bae..f4119ee 100644
--- a/src/test/suite/extension.test.ts
+++ b/src/test/suite/extension.test.ts
@@ -32,7 +32,12 @@ import {
ReferenceCodeLensProvider,
} from "../../providers/css/codelens";
import { parseCss } from "../../parser/v2/css";
-import { CSSProvider } from "../../providers/css/CSSProvider";
+import {
+ CSSCodeLensProvider,
+ CSSProvider,
+ CSSReferenceProvider,
+ CSSRenameProvider,
+} from "../../providers/css/CSSProvider";
import { ProviderKind } from "../../providers/types";
const examplesLocation = "../../../examples/";
@@ -46,6 +51,12 @@ function setWorskpaceFolder(app: string) {
};
}
+const VariousSelectorsModule = path.join(
+ __dirname,
+ examplesLocation,
+ "react-app/src/test/VariousSelectors/VariousSelectors.module.scss"
+);
+
suite("Extension Test Suite", async () => {
window.showInformationMessage("Start all tests.");
const AppComponentUri = Uri.file(
@@ -81,6 +92,12 @@ suite("Extension Test Suite", async () => {
"react-app/src/test/styles/TestStyles.module.scss"
);
+ const VariousSelectorSCssModule = path.join(
+ __dirname,
+ examplesLocation,
+ "react-app/src/test/VariousSelectors/VariousSelectors.module.scss"
+ );
+
const DiagnosticComponent = Uri.file(
path.join(
__dirname,
@@ -368,11 +385,6 @@ suite("Extension Test Suite", async () => {
});
suite("Selector Possibilities", () => {
- const SelectorSCssModule = path.join(
- __dirname,
- examplesLocation,
- "react-app/src/test/VariousSelectors/VariousSelectors.module.scss"
- );
const CSSModule = path.join(
__dirname,
examplesLocation,
@@ -380,11 +392,11 @@ suite("Extension Test Suite", async () => {
);
test("should include normal selectors [no relationship or bound to any rules]", async () => {
- const document = await workspace.openTextDocument(SelectorSCssModule);
+ const document = await workspace.openTextDocument(VariousSelectorsModule);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
const source_css_file = StorageInstance.cssModules.get(
- normalizePath(SelectorSCssModule)
+ normalizePath(VariousSelectorsModule)
);
const node = await parseCss(source_css_file ?? "");
assert.notEqual(node, undefined);
@@ -399,11 +411,11 @@ suite("Extension Test Suite", async () => {
);
});
test("should include selectors from mixins and media queries", async () => {
- const document = await workspace.openTextDocument(SelectorSCssModule);
+ const document = await workspace.openTextDocument(VariousSelectorsModule);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
const source_css_file = StorageInstance.cssModules.get(
- normalizePath(SelectorSCssModule)
+ normalizePath(VariousSelectorsModule)
);
const node = await parseCss(source_css_file ?? "");
assert.notEqual(node, undefined);
@@ -423,11 +435,11 @@ suite("Extension Test Suite", async () => {
});
test("should include selectors from placeholders", async () => {
- const document = await workspace.openTextDocument(SelectorSCssModule);
+ const document = await workspace.openTextDocument(VariousSelectorsModule);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
const source_css_file = StorageInstance.cssModules.get(
- normalizePath(SelectorSCssModule)
+ normalizePath(VariousSelectorsModule)
);
const node = await parseCss(source_css_file ?? "");
assert.notEqual(node, undefined);
@@ -436,11 +448,11 @@ suite("Extension Test Suite", async () => {
});
test("should include suffixed selectors at any depth", async () => {
- const document = await workspace.openTextDocument(SelectorSCssModule);
+ const document = await workspace.openTextDocument(VariousSelectorsModule);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
const source_css_file = StorageInstance.cssModules.get(
- normalizePath(SelectorSCssModule)
+ normalizePath(VariousSelectorsModule)
);
const node = await parseCss(source_css_file ?? "");
assert.notEqual(node, undefined);
@@ -460,11 +472,11 @@ suite("Extension Test Suite", async () => {
});
test("should include camelCased suffixed selectors", async () => {
- const document = await workspace.openTextDocument(SelectorSCssModule);
+ const document = await workspace.openTextDocument(VariousSelectorsModule);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
const source_css_file = StorageInstance.cssModules.get(
- normalizePath(SelectorSCssModule)
+ normalizePath(VariousSelectorsModule)
);
const node = await parseCss(source_css_file ?? "");
assert.notEqual(node, undefined);
@@ -604,7 +616,7 @@ suite("Extension Test Suite", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new CSSProvider({
+ const provider = new CSSReferenceProvider({
document,
position: new Position(3, 11),
providerKind: ProviderKind.References,
@@ -616,7 +628,7 @@ suite("Extension Test Suite", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new CSSProvider({
+ const provider = new CSSReferenceProvider({
document,
position: new Position(11, 11),
providerKind: ProviderKind.References,
@@ -626,81 +638,110 @@ suite("Extension Test Suite", async () => {
});
});
- suite.skip("References", () => {
- test("provide references for a selector at a given position", async () => {
+ suite("Code Lens V2", () => {
+ test("provide code lens for a selectors in a document", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new ReferenceProvider();
- const result = await provider.provideReferences(
+ const provider = new CSSCodeLensProvider({
document,
- new Position(3, 11)
- );
- assert.equal((result ?? []).length, 1);
+ position: new Position(0, 0),
+ providerKind: ProviderKind.CodeLens,
+ });
+ const result = await provider.provideCodeLenses();
+ assert.equal((result ?? []).length > 0, true);
});
- test("provide references for a suffix selector at a given position from multiple modules", async () => {
+ test("provide code lens for a suffix selector in a document", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new ReferenceProvider();
- const result = await provider.provideReferences(
+ let provider = new CSSCodeLensProvider({
document,
- new Position(11, 11)
+ position: new Position(0, 0),
+ providerKind: ProviderKind.CodeLens,
+ });
+ const lenses = await provider.provideCodeLenses();
+ provider = new CSSCodeLensProvider({
+ providerKind: ProviderKind.CodeLens,
+ document,
+ position: new Position(11, 3),
+ });
+ const result = await provider.resolveCodeLens(
+ document.getWordRangeAtPosition(new Position(11, 3))!
);
- assert.equal(result?.length, 4);
+ assert.equal(result.length, 4);
});
});
- suite("Code Lens V2", () => {
- test("provide reference code lens for a selectors in a document", async () => {
+ suite("Rename Selectors", () => {
+ test("should rename a selector at a given position across all its usage", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new CSSProvider({
+ const provider = new CSSRenameProvider({
document,
- position: new Position(0, 0),
- providerKind: ProviderKind.CodeLens,
+ position: new Position(16, 0),
+ providerKind: ProviderKind.RenameSelector,
});
- const result = await provider.provideCodeLenses();
- assert.equal((result ?? []).length > 0, true);
+ const locations = await provider.provideRenameReferences(
+ "testCamelCaseRenamed"
+ );
+ assert.equal(locations.length, 1);
+ assert.equal(locations[0].text, "testCamelCaseRenamed");
+ assert.equal(locations[0].range.start.line, 14);
+ assert.equal(locations[0].range.start.character, 34);
+ assert.equal(locations[0].range.end.line, 14);
+ assert.equal(locations[0].range.end.character, 47);
});
- test("provide references for a suffix selector in a document", async () => {
+
+ test("should rename a selector at a given position across multiple locations", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new CSSProvider({
+ const provider = new CSSRenameProvider({
document,
- position: new Position(0, 0),
- providerKind: ProviderKind.CodeLens,
+ position: new Position(7, 3),
+ providerKind: ProviderKind.RenameSelector,
});
- const lenses = await provider.provideCodeLenses();
- const result = await provider.resolveCodeLens(lenses[3].range);
- assert.equal(result.length, 4);
+ const locations = await provider.provideRenameReferences(
+ "test-sibling-renamed"
+ );
+ assert.equal(locations.length, 3);
});
- });
- suite.skip("Code Lens", () => {
- test("provide reference code lens for a selectors in a document", async () => {
+ test("should rename a suffix selector at a given position with the right combination of parent and suffix", async () => {
const document = await workspace.openTextDocument(TestCssModulePath);
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new ReferenceCodeLensProvider();
- const result = await provider.provideCodeLenses(document, {
- isCancellationRequested: false,
- } as CancellationToken);
- assert.equal((result ?? []).length > 0, true);
+ const provider = new CSSRenameProvider({
+ document,
+ position: new Position(11, 3),
+ providerKind: ProviderKind.RenameSelector,
+ });
+ const locations = await provider.provideRenameReferences(
+ "&-test-suffix-renamed"
+ );
+ assert.equal(locations.length, 4);
+ assert.equal(locations[0].text, "test-container-test-suffix-renamed");
});
- test("provide references for a suffix selector in a document", async () => {
- const document = await workspace.openTextDocument(TestCssModulePath);
+ test("should rename a deeply nested suffix selector at a given position with the right combination of parent and suffix", async () => {
+ const document = await workspace.openTextDocument(
+ VariousSelectorSCssModule
+ );
await window.showTextDocument(document);
await StorageInstance.experimental_BootStrap();
- const provider = new ReferenceCodeLensProvider();
- const lenses = await provider.provideCodeLenses(document, {
- isCancellationRequested: false,
- } as CancellationToken);
- const result = await provider.resolveCodeLens(
- new ReferenceCodeLens(document, document.fileName, lenses[3].range)
+ let provider = new CSSRenameProvider({
+ document,
+ position: new Position(6, 10),
+ providerKind: ProviderKind.RenameSelector,
+ });
+ const locations = await provider.provideRenameReferences(
+ "&-nested-suffix-renamed"
+ );
+ assert.equal(locations.length, 1);
+ assert.equal(
+ locations[0].text,
+ "normal-selector-suffix-nested-suffix-renamed"
);
- assert.equal(result.command?.command, "editor.action.showReferences");
});
});
});