diff --git a/.eslintrc.json b/.eslintrc.json index db7c7b4..ba09b70 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,19 +3,21 @@ "env": { "browser": false, "node": true, - "es6": true + "es6": true, + "jest/globals": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", - "prettier" + "prettier", + "plugin:jest/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, - "plugins": ["@typescript-eslint", "prettier"], + "plugins": ["@typescript-eslint", "prettier", "jest"], "rules": { "@typescript-eslint/camelcase": 0, "@typescript-eslint/explicit-function-return-type": 0, @@ -23,6 +25,7 @@ "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/ban-ts-comment": 1, "prettier/prettier": "error", "no-console":"error" } diff --git a/typings/global.d.ts b/@types/global.d.ts similarity index 100% rename from typings/global.d.ts rename to @types/global.d.ts diff --git a/typings/postcss-modules.d.ts b/@types/postcss-modules.d.ts similarity index 100% rename from typings/postcss-modules.d.ts rename to @types/postcss-modules.d.ts diff --git a/__mocks__/vscode.js b/__mocks__/vscode.js new file mode 100644 index 0000000..615c648 --- /dev/null +++ b/__mocks__/vscode.js @@ -0,0 +1,54 @@ +const CompletionItemKind = { + Color: 15, + Variable: 5, +}; + +class CompletionItem { + /** + * @type {(label: string, kind?: CompletionItemKind | undefined): CompletionItem} + */ + constructor(lable, kind) { + this.label = lable; + this.kind = kind; + } +} + +class Position { + /** + * @param {number} line + * @param {number} char + */ + constructor(line, char) { + this.line = line; + this.character = char; + } + + with = jest.fn().mockImplementation((start, end) => { + return new Range(start, end); + }); +} + +class Range { + /** + * @param {Position} start + * @param {Position} end + */ + constructor(start, end) { + this.start = start; + this.end = end; + } +} + +const workspace = { + getConfiguration: jest.fn(), + workspaceFolders: [], + onDidSaveTextDocument: jest.fn(), +}; + +module.exports = { + CompletionItemKind, + CompletionItem, + workspace, + Position, + Range, +}; diff --git a/package.json b/package.json index 704cf9f..2fafef2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "preview": false, "contributes": { "configuration": { - "title": "CSS Var Complete - An Autocompletion Intellisense tool for CSS Variables", + "title": "CSS Var Complete", "properties": { "cssvar.files": { "type": [ @@ -76,6 +76,15 @@ "type": "boolean", "default": false, "description": "Exclude themed variables" + }, + "cssvar.unstable": { + "type": [ + "array", + "null" + ], + "default": null, + "pattern": "no_sort", + "description": "Enable various unstable feature for the extension" } } } @@ -89,7 +98,7 @@ "watch": "rollup -c -m -w", "lint": "eslint src --ext ts", "lint:fix": "eslint src --ext ts --fix", - "test": "cross-env NODE_ENV=production jest --rootDir src/test" + "test": "cross-env NODE_ENV=production jest" }, "devDependencies": { "@babel/core": "^7.13.15", @@ -110,6 +119,7 @@ "esbuild": "^0.10.2", "eslint": "^7.19.0", "eslint-config-prettier": "^8.1.0", + "eslint-plugin-jest": "^24.4.0", "eslint-plugin-prettier": "^3.3.1", "husky": "^5.2.0", "jest": "^26.6.3", diff --git a/src/constants.ts b/src/constants.ts index a2a92d6..f5ecbc8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -29,11 +29,17 @@ export type SupportedExtensionNames = | "javascript" | "javascriptreact"; +export const UNSTABLE_FEATURES = { + no_sort: false, +}; +export type UnstableFeatures = (keyof typeof UNSTABLE_FEATURES)[]; + export interface Config { files: string[] | Record; extensions: SupportedExtensionNames[]; themes: string[]; excludeThemedVariables: boolean; + unstable: UnstableFeatures; } export const DEFAULT_CONFIG: Config = { @@ -41,6 +47,7 @@ export const DEFAULT_CONFIG: Config = { extensions: ["css", "scss", "sass", "less"], themes: [], excludeThemedVariables: false, + unstable: [], }; export const mapShortToFullExtension = ( diff --git a/src/main.ts b/src/main.ts index 0654617..41bd5d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,8 +12,10 @@ import { SUFFIX, SupportedExtensionNames, SupportedLanguageIds, + UNSTABLE_FEATURES, } from "./constants"; import { getCSSDeclarationArray, isCSSInJS, isObjectProperty } from "./utils"; +import { disableDefaultSort } from "./unstable"; /** * Sets up the Plugin @@ -39,19 +41,19 @@ export async function setup(): Promise<{ config: Config }> { const config: Record> = {} as Config; for (const key in DEFAULT_CONFIG) { if (isObjectProperty(DEFAULT_CONFIG, key)) { - const value = _config.get>(key) || DEFAULT_CONFIG[key]; switch (key) { case "files": { + const value = + _config.get(key) || DEFAULT_CONFIG[key]; if (isMultiRoot) { - const _value = >value; config[key] = []; - for (const workspaceName of Object.keys(_value)) { + for (const workspaceName of Object.keys(value)) { const wFolder = workspace.workspaceFolders.find(w => { return new RegExp(workspaceName).test(w.name); }); if (wFolder) { const _resourcePath = wFolder.uri.fsPath; - const globs = _value[workspaceName]; + const globs = (>value)[workspaceName]; const entries = await fastGlob(globs, { cwd: _resourcePath, }); @@ -70,17 +72,31 @@ export async function setup(): Promise<{ config: Config }> { } break; } - case "extensions": - config[key] = (value).map(ext => { + case "extensions": { + const value = + _config.get(key) || DEFAULT_CONFIG[key]; + config[key] = value.map(ext => { const _ext = ext.startsWith(".") ? (ext.substr(1) as SupportedExtensionNames) : ext; return mapShortToFullExtension(_ext); }); break; - default: + } + case "unstable": { + const value = + _config.get(key) || DEFAULT_CONFIG[key]; + value.forEach(featureKey => { + UNSTABLE_FEATURES[featureKey] = true; + }); + break; + } + default: { + const value = + _config.get(key) || DEFAULT_CONFIG[key]; config[key] = value; break; + } } } } @@ -103,7 +119,7 @@ export interface Region { suffixChar: string; } -export const getRegion = (line: string, currentRange: Range) => { +export const getRegion = (line: string, currentRange: Range): Region | null => { const match = line.match(FILTER_REGEX); if (match) { const filtered = match[1]; @@ -134,7 +150,8 @@ export const createCompletionItems = ( } = { languageId: "css" } ) => { const vars = getCSSDeclarationArray(cssVars); - return vars.reduce((items, cssVar) => { + const size = vars.length; + return vars.reduce((items, cssVar, index) => { const KIND = cssVar.color ? CompletionItemKind.Color : CompletionItemKind.Variable; @@ -158,6 +175,7 @@ export const createCompletionItems = ( } item.insertText = insertText; item.range = options.region.range; + disableDefaultSort(item, { size, index }); } items.push(item); return items; diff --git a/src/test/main.test.ts b/src/test/main.test.ts new file mode 100644 index 0000000..e473406 --- /dev/null +++ b/src/test/main.test.ts @@ -0,0 +1,143 @@ +import { Position, Range } from "vscode"; +import { CSSVarRecord, UNSTABLE_FEATURES } from "../constants"; +import { createCompletionItems, getRegion, Region } from "../main"; + +jest.mock("../constants", () => ({ + ...jest.requireActual("../constants"), + UNSTABLE_FEATURES: { + no_sort: true, + }, +})); + +let region: Region | null = null; + +describe("Test Extension Main", () => { + beforeEach(() => { + region = getRegion( + "color: --s", + new Range(new Position(0, 5), new Position(0, 10)) + ); + }); + + describe(`Test createCompletion method`, () => { + it("Should return CompletionItems with Sorting On", async () => { + UNSTABLE_FEATURES.no_sort = false; + const cssVars: CSSVarRecord = { + "./src/01.css": [ + { + property: "--red-A100", + value: "red", + theme: "", + }, + ], + "./src/02.css": [ + { + property: "--red-500", + value: "red", + theme: "", + }, + ], + }; + const items = createCompletionItems(cssVars, { + region, + languageId: "css", + }); + expect(items[0]).not.toHaveProperty("sortText"); + expect(items[0]).toEqual( + expect.objectContaining({ + insertText: "var(--red-A100);", + }) + ); + }); + it("Should return CompletionItems with Sorting Disabled", async () => { + UNSTABLE_FEATURES.no_sort = true; + const cssVars: CSSVarRecord = { + "./src/01.css": [ + { + property: "--red-A100", + value: "red", + theme: "", + }, + ], + "./src/02.css": [ + { + property: "--red-500", + value: "red", + theme: "", + }, + ], + }; + + const items = createCompletionItems(cssVars, { + region, + languageId: "css", + }); + expect(items[1]).toMatchObject({ + detail: "Value: red", + documentation: "red", + kind: 5, + label: "--red-500", + insertText: "var(--red-500);", + sortText: "1", + }); + }); + it("Should return CompletionItems with 3 and 2digit sortText", async () => { + UNSTABLE_FEATURES.no_sort = true; + const cssVars1: CSSVarRecord = Array(11) + .fill([ + { + property: "--red-A100", + value: "red", + theme: "", + }, + ]) + .reduce((acc, item, index) => { + acc[`./src/${index}.css`] = item; + return acc; + }, {}); + const cssVars2: CSSVarRecord = Array(101) + .fill([ + { + property: "--red-A100", + value: "red", + theme: "", + }, + ]) + .reduce((acc, item, index) => { + acc[`./src/${index}.css`] = item; + return acc; + }, {}); + + const items1 = createCompletionItems(cssVars1, { + region, + languageId: "css", + }); + const items2 = createCompletionItems(cssVars2, { + region, + languageId: "css", + }); + expect(items1[10]).toMatchObject({ + detail: "Value: red", + documentation: "red", + kind: 5, + label: "--red-A100", + sortText: "10", + }); + expect(items1[9]).toEqual( + expect.objectContaining({ + sortText: "09", + }) + ); + expect(items2[99]).toEqual( + expect.objectContaining({ + sortText: "099", + }) + ); + expect(items2[100]).toEqual( + expect.objectContaining({ + sortText: "100", + }) + ); + }); + }); +}); diff --git a/src/unstable.ts b/src/unstable.ts new file mode 100644 index 0000000..166bb3a --- /dev/null +++ b/src/unstable.ts @@ -0,0 +1,19 @@ +/** + * Enable Unstable Features, if user has added cofig for the same. + */ + +import { CompletionItem } from "vscode"; +import { UNSTABLE_FEATURES } from "./constants"; + +export const disableDefaultSort = ( + item: CompletionItem, + options: { + size: number; + index: number; + } +) => { + if (UNSTABLE_FEATURES.no_sort) { + const padSize = Math.floor(Math.log(options.size - 1) / Math.log(10) + 1); + item.sortText = `${options.index}`.padStart(padSize, "0"); + } +}; diff --git a/yarn.lock b/yarn.lock index f546188..f02a873 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1264,6 +1264,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/json-schema@^7.0.7": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + "@types/lodash@^4.14.168": version "4.14.168" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" @@ -1354,6 +1359,18 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" +"@typescript-eslint/experimental-utils@^4.0.1": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz#0ef1d5d86c334f983a00f310e43c1ce4c14e054d" + integrity sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + "@typescript-eslint/parser@^4.14.1": version "4.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.19.0.tgz#4ae77513b39f164f1751f21f348d2e6cb2d11128" @@ -1372,11 +1389,24 @@ "@typescript-eslint/types" "4.19.0" "@typescript-eslint/visitor-keys" "4.19.0" +"@typescript-eslint/scope-manager@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz#9be33aed4e9901db753803ba233b70d79a87fc3e" + integrity sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + "@typescript-eslint/types@4.19.0": version "4.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.19.0.tgz#5181d5d2afd02e5b8f149ebb37ffc8bd7b07a568" integrity sha512-A4iAlexVvd4IBsSTNxdvdepW0D4uR/fwxDrKUa+iEY9UWvGREu2ZyB8ylTENM1SH8F7bVC9ac9+si3LWNxcBuA== +"@typescript-eslint/types@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.0.tgz#9a7c86fcc1620189567dc4e46cad7efa07ee8dce" + integrity sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ== + "@typescript-eslint/typescript-estree@4.19.0": version "4.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.19.0.tgz#8a709ffa400284ab72df33376df085e2e2f61147" @@ -1390,6 +1420,19 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz#4da4cb6274a7ef3b21d53f9e7147cc76f278a078" + integrity sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/visitor-keys@4.19.0": version "4.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.19.0.tgz#cbea35109cbd9b26e597644556be4546465d8f7f" @@ -1398,6 +1441,14 @@ "@typescript-eslint/types" "4.19.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz#4e87b7761cb4e0e627dc2047021aa693fc76ea2b" + integrity sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w== + dependencies: + "@typescript-eslint/types" "4.31.0" + eslint-visitor-keys "^2.0.0" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2071,6 +2122,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: dependencies: ms "2.1.2" +debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2247,6 +2305,13 @@ eslint-config-prettier@^8.1.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz#4ef1eaf97afe5176e6a75ddfb57c335121abc5a6" integrity sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw== +eslint-plugin-jest@^24.4.0: + version "24.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.4.0.tgz#fa4b614dbd46a98b652d830377971f097bda9262" + integrity sha512-8qnt/hgtZ94E9dA6viqfViKBfkJwFHXgJmTWlMGDgunw1XJEGqm3eiPjDsTanM3/u/3Az82nyQM9GX7PM/QGmg== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + eslint-plugin-prettier@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz#7079cfa2497078905011e6f82e8dd8453d1371b7" @@ -2269,6 +2334,13 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -2731,6 +2803,18 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" @@ -4604,7 +4688,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2: +semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -5033,7 +5117,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tsutils@^3.17.1: +tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==