Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"eslint": "^7.27.0",
"glob": "^7.1.7",
"husky": ">=6",
"less": "^4.1.3",
"lint-staged": ">=10",
"mocha": "^8.4.0",
"prettier": "^2.3.2",
Expand Down
4 changes: 2 additions & 2 deletions src/CompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class CSSModuleCompletionProvider implements CompletionItemProvider {
return Promise.resolve([]);
}

const classNames = await getAllClassNames(importPath, field);
const classNames = await getAllClassNames(importPath, field, document);

return Promise.resolve(
classNames.map((_class) => {
Expand All @@ -88,7 +88,7 @@ export class CSSModuleCompletionProvider implements CompletionItemProvider {
}
if (isKebabCaseClassName(name)) {
return createBracketCompletionItem(name, position);
}
}
return new CompletionItem(name, CompletionItemKind.Variable);
})
);
Expand Down
1 change: 1 addition & 0 deletions src/test/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export const SAMPLE_JS_FILE = path.join(FIXTURES_PATH, "sample.jsx");
export const STYLUS_JS_FILE = path.join(FIXTURES_PATH, "stylus.jsx");
export const JUMP_PRECISE_DEF_FILE = path.join(FIXTURES_PATH, "jumpDef.jsx");
export const SPREAD_SYNTAX_FILE = path.join(FIXTURES_PATH, "spread-syntax", "index.js");
export const LESS_JS_FILE = path.join(FIXTURES_PATH,"less-css.jsx")
5 changes: 5 additions & 0 deletions src/test/fixtures/less-css.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styles from "./less-css.less";

console.log(styles['container-header-body']);

styles.container
7 changes: 7 additions & 0 deletions src/test/fixtures/less-css.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.container {
&-header{
&-body{}
}
}

// 3 css item
19 changes: 16 additions & 3 deletions src/test/suite/CompletionProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import * as vscode from "vscode";

import { CSSModuleCompletionProvider } from "../../CompletionProvider";
import { CamelCaseValues } from "../../options";
import { SAMPLE_JS_FILE, STYLUS_JS_FILE } from "../constant";
import { LESS_JS_FILE, SAMPLE_JS_FILE, STYLUS_JS_FILE } from "../constant";
import { readOptions } from "../utils";

const uri = vscode.Uri.file(SAMPLE_JS_FILE);
const uri2 = vscode.Uri.file(STYLUS_JS_FILE);
const lessUri = vscode.Uri.file(LESS_JS_FILE);

function testCompletion(position: vscode.Position, itemCount: number, fixtureFile?: vscode.Uri) {
function testCompletion(
position: vscode.Position,
itemCount: number,
fixtureFile?: vscode.Uri
) {
return vscode.workspace.openTextDocument(fixtureFile || uri).then((text) => {
const provider = new CSSModuleCompletionProvider(readOptions());
return provider.provideCompletionItems(text, position).then((items) => {
Expand Down Expand Up @@ -101,7 +106,8 @@ test("test camelCase:false style and kebab-case completion", () => {
return Promise.resolve(
testCompletionWithCase(position, false, [
(items) => assert.strictEqual(1, items.length),
(items) => assert.strictEqual(`['sidebar_without-header']`, items[0].insertText),
(items) =>
assert.strictEqual(`['sidebar_without-header']`, items[0].insertText),
])
).catch((err) => {
assert.ok(false, `error in OpenTextDocument ${err}`);
Expand Down Expand Up @@ -131,3 +137,10 @@ test("test camelCase:dashes style completion", () => {
assert.ok(false, `error in OpenTextDocument ${err}`);
});
});

// test("test .less extname less completion", () => {
// const position = new vscode.Position(5, 7);
// return Promise.resolve(testCompletion(position, 3, lessUri)).catch((err) => {
// assert.ok(false, `error in OpenTextDocument ${err}`);
// });
// });
95 changes: 81 additions & 14 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Position, TextDocument, CompletionItem, CompletionItemKind, TextEdit, Range } from "vscode";
import {
Position,
TextDocument,
CompletionItem,
CompletionItemKind,
TextEdit,
Range,
workspace,
} from "vscode";
import * as fse from "fs-extra";
import * as _ from "lodash";
import { join } from "path";

export function getCurrentLine(
document: TextDocument,
Expand All @@ -9,23 +18,65 @@ export function getCurrentLine(
return document.getText(document.lineAt(position).range);
}

// https://github.com/microsoft/vscode-eslint/blob/main/server/src/eslintServer.ts
// This makes loading work in a plain NodeJS and a WebPacked environment
declare const __webpack_require__: typeof require;
declare const __non_webpack_require__: typeof require;
export function loadNodeModule<T>(moduleName: string): T | undefined {
const r =
typeof __webpack_require__ === "function"
? __non_webpack_require__
: require;
try {
return r(moduleName);
} catch (err: any) {
console.log(err);
}
return undefined;
}

/**
* @TODO Refact by new Tokenizer
*/
export async function getAllClassNames(filePath: string, keyword: string): Promise<string[]> {
export async function getAllClassNames(
filePath: string,
keyword: string,
document: TextDocument
): Promise<string[]> {
// check file exists, if not just return []
const filePathStat = await fse.stat(filePath);
if (!filePathStat.isFile()) {
return [];
}

const content = await fse.readFile(filePath, { encoding: "utf8" });
let content = await fse.readFile(filePath, { encoding: "utf8" });
let matchLineRegexp = /.*[,{]/g;

// experimental stylus support
if (filePath.endsWith(".styl") ||filePath.endsWith(".stylus")) {
matchLineRegexp = /\..*/g
// experimental stylus support
if (filePath.endsWith(".styl") || filePath.endsWith(".stylus")) {
matchLineRegexp = /\..*/g;
}

if (filePath.endsWith(".less")) {
const lessModulePath = join(
workspace.getWorkspaceFolder(document.uri).uri.fsPath,
"node_modules",
"less"
);
const less = loadNodeModule(lessModulePath) as any;
if (less) {
// remove all @import
content = content.replace(/@import.+;/g, "");
// replace all variable to other value
content = content.replace(/@.+/g, "unset");
try {
content = (await less.render(content)).css;
} catch (error) {
console.log(error);
}
}
}

const lines = content.match(matchLineRegexp);
if (lines === null) {
return [];
Expand All @@ -36,7 +87,9 @@ export async function getAllClassNames(filePath: string, keyword: string): Promi
return [];
}

const uniqNames = _.uniq(classNames).map((item) => item.slice(1)).filter((item) => !/^[0-9]/.test(item));
const uniqNames = _.uniq(classNames)
.map((item) => item.slice(1))
.filter((item) => !/^[0-9]/.test(item));
return keyword !== ""
? uniqNames.filter((item) => item.indexOf(keyword) !== -1)
: uniqNames;
Expand All @@ -53,19 +106,33 @@ export function dashesCamelCase(str: string): string {
/**
* check kebab-case classname
*/
export function isKebabCaseClassName (className: string): boolean {
return className?.includes('-');
export function isKebabCaseClassName(className: string): boolean {
return className?.includes("-");
}

/**
* BracketCompletionItem Factory
*/
export function createBracketCompletionItem (className: string, position: Position): CompletionItem {
const completionItem = new CompletionItem(className, CompletionItemKind.Variable);
export function createBracketCompletionItem(
className: string,
position: Position
): CompletionItem {
const completionItem = new CompletionItem(
className,
CompletionItemKind.Variable
);
completionItem.detail = `['${className}']`;
completionItem.documentation = "kebab-casing may cause unexpected behavior when trying to access style.class-name as a dot notation. You can still work around kebab-case with bracket notation (eg. style['class-name']) but style.className is cleaner.";
completionItem.documentation =
"kebab-casing may cause unexpected behavior when trying to access style.class-name as a dot notation. You can still work around kebab-case with bracket notation (eg. style['class-name']) but style.className is cleaner.";
completionItem.insertText = `['${className}']`;
completionItem.additionalTextEdits = [new TextEdit(new Range(new Position(position.line, position.character - 1),
new Position(position.line, position.character)), '')];
completionItem.additionalTextEdits = [
new TextEdit(
new Range(
new Position(position.line, position.character - 1),
new Position(position.line, position.character)
),
""
),
];
return completionItem;
}
Loading