diff --git a/README.md b/README.md index 3abee55..134dcfc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ Extension for [CSS Modules](https://github.com/css-modules/css-modules), which s - Autocomplete - Go to definition +## Additional Features + +- **Experimental:** SASS (.sass) file support for class name completion + ## Demo ![](https://i.giphy.com/l0EwY2Mk4IBgIholi.gif) diff --git a/src/test/constant.ts b/src/test/constant.ts index cff060a..b0220f9 100644 --- a/src/test/constant.ts +++ b/src/test/constant.ts @@ -10,4 +10,9 @@ export const SAMPLE_TS_FILE = path.join(FIXTURES_PATH, "sample.ts"); export const SAMPLE_ASTRO_FILE = path.join(FIXTURES_PATH, "sample.astro"); export const STYLUS_JSX_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 SPREAD_SYNTAX_FILE = path.join( + FIXTURES_PATH, + "spread-syntax", + "index.js" +); +export const SASS_JSX_FILE = path.join(FIXTURES_PATH, "sass.jsx"); diff --git a/src/test/fixtures/sample.sass b/src/test/fixtures/sample.sass new file mode 100644 index 0000000..b6fd3d0 --- /dev/null +++ b/src/test/fixtures/sample.sass @@ -0,0 +1,20 @@ +.foo + color: red + +.bar, .baz + background: blue + +.parent + &.active + color: green + & .activeSpace + color: blue + > .child + color: yellow + + .sibling + color: orange + ~ .other + color: purple + + .complex, &.compound, >.direct, +.adjacent, ~.general + border: 1px solid black \ No newline at end of file diff --git a/src/test/fixtures/sass.jsx b/src/test/fixtures/sass.jsx new file mode 100644 index 0000000..357be4f --- /dev/null +++ b/src/test/fixtures/sass.jsx @@ -0,0 +1,21 @@ +import styles from "./sample.sass"; + +const Component = () => { + return ( +
+
Bar
+
Baz
+
Active
+
Child
+
Sibling
+
Other
+
Complex
+
Compound
+
Direct
+
Adjacent
+
General
+
+ ); +}; + +export default Component; \ No newline at end of file diff --git a/src/test/suite/CompletionProvider.test.ts b/src/test/suite/CompletionProvider.test.ts index 419bd35..9e028f8 100644 --- a/src/test/suite/CompletionProvider.test.ts +++ b/src/test/suite/CompletionProvider.test.ts @@ -10,6 +10,7 @@ import { SAMPLE_TSX_FILE, SAMPLE_TS_FILE, STYLUS_JSX_FILE, + SASS_JSX_FILE, } from "../constant"; import { readOptions } from "../utils"; @@ -19,6 +20,7 @@ const uri3 = vscode.Uri.file(SAMPLE_JS_FILE); const uri4 = vscode.Uri.file(SAMPLE_TSX_FILE); const uri5 = vscode.Uri.file(SAMPLE_TS_FILE); const uri6 = vscode.Uri.file(SAMPLE_ASTRO_FILE); +const uri7 = vscode.Uri.file(SASS_JSX_FILE); function testCompletion( position: vscode.Position, @@ -210,3 +212,10 @@ test("support astro", () => { assert.ok(false, `error in OpenTextDocument ${err}`); }); }); + +test("support .sass completion", () => { + const position = new vscode.Position(4, 27); + return Promise.resolve(testCompletion(position, 14, uri7)).catch((err) => { + assert.ok(false, `error in OpenTextDocument ${err}`); + }); +}); diff --git a/src/utils/index.ts b/src/utils/index.ts index f352505..1a71eb4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,11 @@ -import { Position, TextDocument, CompletionItem, CompletionItemKind, TextEdit, Range } from "vscode"; +import { + Position, + TextDocument, + CompletionItem, + CompletionItemKind, + TextEdit, + Range, +} from "vscode"; import * as fse from "fs-extra"; import * as _ from "lodash"; @@ -12,7 +19,10 @@ export function getCurrentLine( /** * @TODO Refact by new Tokenizer */ -export async function getAllClassNames(filePath: string, keyword: string): Promise { +export async function getAllClassNames( + filePath: string, + keyword: string +): Promise { // check file exists, if not just return [] const filePathStat = await fse.stat(filePath); if (!filePathStat.isFile()) { @@ -22,10 +32,16 @@ export async function getAllClassNames(filePath: string, keyword: string): Promi const 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; } + + // experimental sass support + if (filePath.endsWith(".sass")) { + matchLineRegexp = /(^|[\s,&>+~])\.[_A-Za-z0-9-]+/gm; + } + const lines = content.match(matchLineRegexp); if (lines === null) { return []; @@ -36,7 +52,10 @@ 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; @@ -53,19 +72,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; }