├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .husky ├── .gitignore ├── pre-commit └── pre-push ├── .nvmrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc.yml ├── @types ├── common.d.ts ├── culori.d.ts ├── follow-redirects.d.ts ├── global.d.ts └── postcss.d.ts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TASKS.md ├── __mocks__ └── vscode.js ├── babel.config.js ├── docs ├── customize-extension.md ├── debug-extension.md └── theming.md ├── esbuild.sh ├── eslint.config.mjs ├── examples ├── basics │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── global.css │ ├── index.html │ ├── local.css │ ├── spacing.css │ └── theme.css ├── css-extensions │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.css │ ├── package.json │ ├── styles.scss │ └── theme.scss ├── css-imports │ ├── .vscode │ │ └── settings.json │ ├── index.css │ ├── local.css │ ├── nested.css │ ├── package.json │ └── pnpm-lock.yaml ├── css-in-node-modules │ ├── .vscode │ │ └── settings.json │ ├── index.css │ ├── package.json │ └── pnpm-lock.yaml ├── custom-extension │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.styl │ ├── package.json │ ├── pnpm-lock.yaml │ └── variables.styl ├── js-parser │ ├── .vscode │ │ └── settings.json │ ├── index.ejs │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── App.tsx │ │ └── global │ │ │ ├── BrandStyles.tsx │ │ │ ├── Layout.tsx │ │ │ ├── Theme.tsx │ │ │ └── ThemeOverrides.tsx │ └── tsconfig.json ├── multi-root │ ├── README.md │ ├── multi-root.code-workspace │ ├── package.json │ ├── pnpm-lock.yaml │ ├── proj-1 │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── broken.css │ │ └── index.css │ ├── proj-2 │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── index.scss │ │ └── theme.scss │ └── proj-3 │ │ ├── .vscode │ │ └── settings.json │ │ ├── app.css │ │ ├── index.css │ │ ├── theme.css │ │ └── theme.dark.css ├── recursion │ ├── .vscode │ │ └── settings.json │ ├── 01-defined-early.css │ ├── 02-var-fn-not-evaluated.css │ ├── 03-no-value-defined.css │ ├── 04-defined-value-after-use.css │ ├── testing-base.css │ ├── testing-index.css │ ├── testing-solo.css │ └── testing-temp.css ├── safe-parser │ ├── .vscode │ │ └── settings.json │ ├── index.css │ ├── source.astro │ ├── source.jsx │ ├── source.scss │ └── source.tsx ├── scss │ ├── .vscode │ │ └── settings.json │ ├── _01-variables.scss │ ├── _02-variables.scss │ ├── _base.scss │ ├── _variables.scss │ └── index.scss ├── theming │ ├── .vscode │ │ └── settings.json │ ├── styles.css │ └── theme.css ├── tokencss-plugin │ ├── .vscode │ │ └── settings.json │ ├── base.css │ ├── config │ │ └── token.config.json │ ├── index.css │ ├── package.json │ └── pnpm-lock.yaml └── with-plugins │ ├── .vscode │ └── settings.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── index.css │ ├── other.css │ ├── tailwind.css │ ├── testing.js │ └── tokencss.css │ ├── tailwind.config.js │ └── token.config.json ├── feature-contributions.md ├── img ├── icon.png └── sponsors.png ├── jest-global-setup.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── rollup.config.mjs ├── src ├── color-parser.ts ├── constants.ts ├── errors.ts ├── extension.ts ├── logger.ts ├── main.ts ├── parser.ts ├── pre-processors │ ├── index.ts │ └── template-literals.ts ├── providers │ ├── color-provider.ts │ ├── completion-provider.ts │ ├── definition-provider.ts │ ├── diagnostics.ts │ └── hover-provider.ts ├── remote-paths.ts ├── scripts │ ├── create-changelog.js │ ├── publish.js │ └── yarn-berry-list.js ├── test │ ├── at-rules.css │ ├── broken.css │ ├── color-parser.test.ts │ ├── color-provider │ │ └── color-provider.test.ts │ ├── config.test.ts │ ├── css-imports │ │ ├── _f3.scss │ │ ├── _f4.scss │ │ ├── f1.css │ │ ├── f1.scss │ │ ├── f2.css │ │ ├── f2.scss │ │ ├── f3.css │ │ ├── f4.css │ │ ├── import.css │ │ ├── import.scss │ │ └── nested │ │ │ ├── _f5.scss │ │ │ └── f6.scss │ ├── definition-provider.test.ts │ ├── extension.test.ts │ ├── fixtures │ │ ├── edge-cases.jsx │ │ └── theming.css │ ├── main.test.ts │ ├── parser.test.ts │ ├── pre-processors │ │ └── template-literals.test.ts │ ├── providers │ │ └── diagnostics.test.ts │ ├── renamed.css │ ├── test-utilities.ts │ ├── theming-and-duplicates.test.ts │ ├── touch.css │ └── utils.test.ts ├── third-party │ ├── index.ts │ ├── safe-parser.ts │ └── scss │ │ ├── LICENSE │ │ ├── nested-declaration.ts │ │ ├── parser.ts │ │ ├── safe-scss-parse.ts │ │ └── scss-tokenize.ts ├── unstable.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [phoenisx] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: phoenisx 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Details (please complete the following information):** 27 | - OS: [e.g. Windows, Linux] 28 | - VSCode version [e.g. 1.70.2] 29 | - Extension version [e.g. 2.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feat]" 5 | labels: enhancement 6 | assignees: phoenisx 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | yarn-error.log 7 | .DS_Store 8 | coverage/ 9 | .env 10 | temp 11 | .yarn 12 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run lint 2 | npm run test 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.18 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}", 14 | "--disable-extension=phoenisx.cssvar" 15 | ], 16 | "resolveSourceMapLocations": [ 17 | "${workspaceFolder}/**", 18 | "!**/node_modules/**" 19 | ], 20 | "outFiles": [ 21 | "${workspaceFolder}/out/**/*.js", 22 | "!**/node_modules/**" 23 | ], 24 | "skipFiles": ["**/node_modules/**"], 25 | "preLaunchTask": "npm: watch" 26 | }, 27 | { 28 | "name": "Extension Tests", 29 | "type": "extensionHost", 30 | "request": "launch", 31 | "args": [ 32 | "--extensionDevelopmentPath=${workspaceFolder}", 33 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index", 34 | "--disable-extension=phoenisx.cssvar" 35 | ], 36 | "outFiles": [ 37 | "${workspaceFolder}/out/test/**/*.js" 38 | ], 39 | "preLaunchTask": "npm: test" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "cssvar.ignore": [ 12 | "**/test/**.css", 13 | "**/examples/**", 14 | "node_modules/**" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "presentation": { 6 | "echo": false, 7 | "reveal": "always", 8 | "focus": false, 9 | "panel": "dedicated", 10 | "showReuseMessage": false 11 | }, 12 | "tasks": [ 13 | { 14 | "type": "npm", 15 | "script": "watch", 16 | "isBackground": true, 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | }, 21 | "problemMatcher": { 22 | "owner": "typescript", 23 | "fileLocation": "autoDetect", 24 | "pattern": [ 25 | { 26 | "regexp": "(.*?):(\\d+):(\\d+):\\s+(error|warn):\\s+(.*)", 27 | "file": 1, 28 | "line": 2, 29 | "column": 3, 30 | "severity": 4, 31 | "message": 5 32 | } 33 | ], 34 | "background": { 35 | "activeOnStart": true, 36 | "beginsPattern": "^bundles\\s+(.*?)\\s+→\\s+(.*)...", 37 | "endsPattern": "^\\[\\d+-\\d+-\\d+\\s+\\d+:\\d+:\\d+\\]\\s+waiting for changes..." 38 | }, 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | examples/** 6 | node_modules/** 7 | .husky/** 8 | .gitignore 9 | .yarnrc 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/.eslintrc.json 13 | prettier.config.js 14 | rollup.config.js 15 | **/*.map 16 | **/*.ts 17 | **/*.sh 18 | yarn-error.log 19 | __mocks__ 20 | babel.config.js 21 | coverage 22 | jest.config.js 23 | TASKS.md 24 | temp 25 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /@types/culori.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 3 | 4 | // @ts-ignore 5 | /// 6 | 7 | /** 8 | * Obviously following is incorrect, 9 | * but it will help me remember how to write 10 | * .d.ts module files 11 | */ 12 | declare module "culori" { 13 | export * from "culori/fn"; 14 | } 15 | -------------------------------------------------------------------------------- /@types/follow-redirects.d.ts: -------------------------------------------------------------------------------- 1 | declare module "follow-redirects" { 2 | const http: typeof import("http"); 3 | const https: typeof import("https"); 4 | 5 | export { http, https }; 6 | } 7 | -------------------------------------------------------------------------------- /@types/global.d.ts: -------------------------------------------------------------------------------- 1 | type ValueOf = T[keyof T]; 2 | -------------------------------------------------------------------------------- /@types/postcss.d.ts: -------------------------------------------------------------------------------- 1 | type _Token = [ 2 | tokenName: string, 3 | value?: string, 4 | startLine?: number, 5 | startCol?: number, 6 | endLine?: string | number, 7 | enCol?: string | number 8 | ]; 9 | 10 | type _Tokenizer = { 11 | back: (token: any) => void; 12 | nextToken: (opts?: any) => _Token | undefined; 13 | endOfFile: () => boolean; 14 | position: () => number; 15 | }; 16 | 17 | type TokenizerOptions = { 18 | ignoreErrors?: boolean; 19 | }; 20 | 21 | declare module "postcss/lib/tokenize" { 22 | import { Input } from "postcss"; 23 | export default function tokenizer( 24 | input: Input, 25 | options?: import("postcss").ProcessOptions & TokenizerOptions 26 | ): Tokenizer; 27 | export type Token = _Token; 28 | export type Tokenizer = _Tokenizer; 29 | } 30 | 31 | declare module "postcss/lib/parser" { 32 | import { Root, Input, Node } from "postcss"; 33 | 34 | class Parser { 35 | root: Root; 36 | input: Input; 37 | tokenizer: _Tokenizer; 38 | spaces: string; 39 | current: Node; 40 | semicolon: boolean; 41 | customProperty: boolean; 42 | 43 | constructor(input: Input); 44 | createTokenizer(): void; 45 | parse(): void; 46 | 47 | comment(token: _Token): void; 48 | emptyRule(token: _Token): void; 49 | other(start: number): void; 50 | rule(tokens: _Token[]): void; 51 | decl(tokens: _Token[], customProperty?: string): void; 52 | atrule(token: _Token): void; 53 | end(token: _Token): void; 54 | endFile(): void; 55 | freeSemicolon(token: _Token): void; 56 | 57 | // Helpers 58 | getPosition(offset: number): { 59 | offset: number; 60 | line: number; 61 | column: number; 62 | }; 63 | init(node: Node, offset: any): void; 64 | raw(node: Node, prop: string, tokens: _Token[], customProperty: any): void; 65 | spacesAndCommentsFromEnd(tokens: _Token[]): string; 66 | spacesAndCommentsFromStart(tokens: _Token[]): string; 67 | spacesFromEnd(tokens: _Token[]): string; 68 | stringFrom(tokens: _Token[], from: any): string; 69 | colon(tokens: _Token[]): false | number; 70 | 71 | // Errors 72 | unclosedBracket(bracket: number[]): void; 73 | unknownWord(tokens: _Token[]): void; 74 | unexpectedClose(token: _Token): void; 75 | unclosedBlock(): void; 76 | doubleColon(token: _Token): void; 77 | unnamedAtrule(node: Node, token: _Token): void; 78 | precheckMissedSemicolon(tokens?: _Token[]): void; 79 | checkMissedSemicolon(tokens: _Token[]): void; 80 | } 81 | export default Parser; 82 | } 83 | 84 | declare module "@tokencss/postcss" { 85 | export default function plugin(options?: any): void; 86 | } 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to VSCode CSSVar extension 2 | [Draft] 3 | 4 | Thanks for your support and taking time to contribute! 🎉👍 5 | 6 | ## Prerequisites 7 | 8 | ## Release/Deployments 9 | 10 | Currently Deployments are managed manually by @phoenisx. I might make this more public in future, for now 11 | keeping it manual was the most easiest way for me. 12 | 13 | ### Releasing a new version 14 | 15 | It's a two step process (and has a lot of room for improvement) 16 | 17 | I maintain two custom scripts: 18 | 19 | **`src/scripts/create-changelog.js`** 20 | This is required to automate creating changelog once all the changes are merged to `main` and are about to be released. 21 | This script assumes that we already have previous release version tag present in git history where the command is run. 22 | 23 | We can run the following: 24 | ```sh 25 | node src/scripts/create-changelog.js v2.6.4..HEAD | pbcopy 26 | ``` 27 | to generate changelog between previous release and latest commit. 28 | If the generated content is copied to clipboard we can use that content to modify our `CHANGELOG.md`. 29 | 30 | Once we are satisfied with the updated CHANGELOG, we can commit the changes and create a new release on Github 31 | 32 | > Note we can either create a tag ourself manually or release the previous commit using github and create a tag 33 | > remotely and then creating this changelog with the remote tag. 34 | 35 | Follow up commands (not necessary) 36 | ```sh 37 | yarn version patch 38 | # Commit changes 39 | git tag -a v2.6.5 40 | git push origin main --follow-tags 41 | ``` 42 | 43 | **`src/scripts/publish.js`** 44 | 45 | Publishing our release to VSCode/OVSX extension marketplaces. 46 | We need the private tokens for both these marketplaces for this command to work. 47 | 48 | Before running this script the following steps are necessary: 49 | - [Learn how to publish extension on VSCode marketplace](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) 50 | - [Publishing Extension on OVSX Marketplace](https://github.com/eclipse/openvsx/wiki/Publishing-Extensions) 51 | - Install following CLI tools: `vsce` and `ovsx` 52 | 53 | Running this script will than auto publish our changes to both these marketplaces. 54 | 55 | ```sh 56 | vsce login {publisher} # Required 57 | OVSX_KEY={token} ./src/scripts/publish.js 58 | ``` 59 | 60 | The above command requires everything to be packaged into a single script using `esbuild`, since I am using `yarn` v4 (berry) and `vsce` has issues with yarn v2+ 61 | Ref: https://github.com/microsoft/vscode-vsce/issues/517 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2021-present Subroto Biswas 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | CssVar Icon 6 |
7 | 8 | 9 |

10 | CSS Variables 11 |

12 | 13 |
14 |

15 | Please vote/rate and star this project to show your support. 16 | ❤️ 17 |

18 | 19 | 20 | 21 | 22 | 24 |    25 | 26 | 27 | 28 |    29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 |    37 | 38 | 39 | 40 |
41 | 42 | ## 💡 Features: 43 | 44 | This extension helps provide autocompletion IntelliSense 45 | for globally shared CSS Variables and more. 46 | 47 | ### How the extension works: 48 | - When adding a new CSS declaration property value, press `--`. 49 | - This opens a completion list dropdown, with all the CSS variables in the list. 50 | - Select and add the variable of your choice, to autocomplete. 51 | 52 |
53 | 54 | ### Auto Completion, Color swatches, Goto Definition 55 | 56 | This extension has in-built support for parsing: `css`, `scss`, `less`, `js`, `jsx`, `ts`, `tsx` 57 | source file extensions and providing CSS variable suggestions from them. 58 | 59 | Details can be read in [Customization Doc][customize-extension-link]. 60 | 61 | 62 |

63 | Autocomplete, Color Swatches 68 | Variables Goto Definition 73 |

74 | 75 |
76 | 77 | ### Customization 78 | 79 | If your project uses `sass` or `styl` or some other custom source file extension, and you are 80 | facing issues to setup this VSCode extension, please read 81 | [Customization][customize-extension-link] Doc. 82 | 83 |
84 | 85 | ### Theme Support 86 | 87 | Read more about [Theming here][theme-link] 88 | 89 |

90 | Theming 95 | Exclude Duplicates 100 |

101 | 102 |
103 | 104 | ### CSS Level 4 color spec support 105 | 106 | Limited support to keep bundle size small. 107 |
108 | Except for `color()` api, every other CSS color is supported. 109 | Please find details for CSS colors [here in MDN Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) 110 | 111 |
112 | 113 | ## 🔖 Appendix: 114 | 115 | - [Extension setting details][settings-link] 116 | - [Extension Customization][customize-extension-link] 117 | - [Theming Support][theme-link] 118 | - [Debugging the extension][debug-link] 119 | 120 |



121 | 122 | 129 | 130 | 131 | 132 | [settings-link]: https://github.com/willofindie/vscode-cssvar/blob/main/feature-contributions.md 133 | [customize-extension-link]: https://github.com/willofindie/vscode-cssvar/blob/main/docs/customize-extension.md 134 | [theme-link]: https://github.com/willofindie/vscode-cssvar/blob/main/docs/theming.md 135 | [debug-link]: https://github.com/willofindie/vscode-cssvar/blob/main/docs/debug-extension.md 136 | -------------------------------------------------------------------------------- /TASKS.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | - Implement Github Workflows to auto release with notes: check https://github.com/MylesBorins/node-osc 3 | - Immediately trigger completion intellisense for CSS, where this extension triggers, and we are still on a valid string, i.e. 4 | after `:` and before `;` 5 | - This is required because VSCode triggers it's own intellisense in CSS files, when declarations are auto completed, 6 | which doesn't trigger the completion provider for this extension. 7 | - Add support for CSS @property at-rule: https://developer.mozilla.org/en-US/docs/Web/CSS/@property 8 | - `statsSync` used for cache invalidation needs to be improvised for failure cases 9 | - This can happen when a file is deleted and does not exist anymore. 10 | - Add compatibility with https://github.com/enyancc/vscode-ext-color-highlight 11 | - Precached files in file watcher, once removed from source css files (like from @import paths) 12 | still stays cached. Steps to reproduce: 13 | - Open `css-imports` project -> After the files are parsed, update any import for `open-props` with a new 14 | granular css import, like `open-props/red.min.css`. 15 | - Doing this still shows `open-props/open-props.min.css` in the autocomplete/definition-provider list. 16 | - Support variable defaults syntax as well, like the following: 17 | - `color: var(--color, #333)`, this is a valid syntax. 18 | - `cssvar.enable` disables the extension entirely. It is not scoped for each root folder. 19 | 20 | 21 | ## Notes: 22 | 23 | - Good read to understand CSS Syntax: [Tokenizer and Parser](https://drafts.csswg.org/css-syntax/) 24 | - Good vscode extension to take insights from 25 | - https://github.com/znck/grammarly/blob/main/package.json 26 | - https://stackoverflow.com/a/15673308/2849127 27 | -------------------------------------------------------------------------------- /__mocks__/vscode.js: -------------------------------------------------------------------------------- 1 | const CompletionItemKind = { 2 | Color: 15, 3 | Variable: 5, 4 | }; 5 | 6 | class CompletionItem { 7 | /** 8 | * @type {(label: string, kind?: CompletionItemKind | undefined): CompletionItem} 9 | */ 10 | constructor(lable, kind) { 11 | this.label = lable; 12 | this.kind = kind; 13 | } 14 | } 15 | 16 | class CompletionList { 17 | constructor(items) { 18 | this.items = items; 19 | } 20 | } 21 | 22 | class Position { 23 | /** 24 | * @param {number} line 25 | * @param {number} char 26 | */ 27 | constructor(line, char) { 28 | this.line = line; 29 | this.character = char; 30 | } 31 | 32 | /** 33 | * @param {number} line 34 | * @param {number} char 35 | */ 36 | with = jest.fn().mockImplementation((line, char) => { 37 | // Reference of https://github.com/microsoft/vscode/blob/main/src/vs/editor/common/core/position.ts 38 | if (line === this.line && char === this.char) { 39 | return this; 40 | } else { 41 | if ( 42 | typeof line === "object" && 43 | Object.prototype.hasOwnProperty.call(line, "line") 44 | ) { 45 | const { line: _line, character } = line; 46 | return new Position(_line, character); 47 | } 48 | return new Position(line, char); 49 | } 50 | }); 51 | 52 | translate = jest.fn().mockImplementation(change => { 53 | if (typeof change !== "object") { 54 | return this; 55 | } else { 56 | const { lineDelta, characterDelta } = change; 57 | return new Position( 58 | this.line + lineDelta, 59 | this.character + characterDelta 60 | ); 61 | } 62 | }); 63 | } 64 | 65 | class Range { 66 | /** 67 | * @param {Position} start 68 | * @param {Position} end 69 | */ 70 | constructor(start, end) { 71 | this.start = start; 72 | this.end = end; 73 | } 74 | } 75 | 76 | class Location { 77 | /** 78 | * @param {Uri} uri 79 | * @param {Rage | Position} range 80 | */ 81 | constructor(uri, range) { 82 | this.uri = uri; 83 | this.range = range; 84 | } 85 | } 86 | 87 | class Color { 88 | constructor(red, green, blue, alpha) { 89 | this.red = red; 90 | this.green = green; 91 | this.blue = blue; 92 | this.alpha = alpha; 93 | } 94 | } 95 | 96 | class Diagnostic { 97 | constructor(range, desc, severity) { 98 | this.range = range; 99 | this.desc = desc; 100 | this.severity = severity; 101 | } 102 | } 103 | 104 | const workspace = { 105 | getConfiguration: jest.fn(), 106 | workspaceFolders: [], 107 | onDidSaveTextDocument: jest.fn(), 108 | onDidChangeTextDocument: jest.fn(), 109 | onDidCloseTextDocument: jest.fn(), 110 | createFileSystemWatcher: jest.fn().mockReturnValue({ 111 | onDidChange: jest.fn(), 112 | onDidDelete: jest.fn(), 113 | }), 114 | }; 115 | 116 | const languages = { 117 | registerCompletionItemProvider: jest.fn((_, obj) => obj), 118 | registerColorProvider: jest.fn((_, obj) => obj), 119 | registerDefinitionProvider: jest.fn((_, obj) => obj), 120 | registerHoverProvider: jest.fn((_, obj) => obj), 121 | createDiagnosticCollection: jest.fn((_, obj) => obj), 122 | }; 123 | 124 | const window = { 125 | // eslint-disable-next-line no-console 126 | showErrorMessage: jest.fn(msg => console.trace(msg)), 127 | // For now I am not testing switching between muti-roots 128 | onDidChangeActiveTextEditor: cb => { 129 | cb && cb(); 130 | }, 131 | }; 132 | 133 | const Uri = { 134 | parse: path => path, 135 | file: path => path, 136 | }; 137 | 138 | export class RelativePattern { 139 | constructor(base, pattern) { 140 | this.base = base; 141 | this.pattern = pattern; 142 | } 143 | } 144 | 145 | module.exports = { 146 | CompletionItemKind, 147 | CompletionItem, 148 | CompletionList, 149 | workspace, 150 | languages, 151 | window, 152 | Position, 153 | Range, 154 | Color, 155 | Location, 156 | EndOfLine: { 157 | LF: 1, 158 | CRLF: 2, 159 | }, 160 | Uri, 161 | RelativePattern, 162 | DiagnosticSeverity: { 163 | Error: 0, 164 | Warning: 1, 165 | }, 166 | Diagnostic, 167 | }; 168 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Babel is used specifically to write tests in JS and 3 | * support Typescript in jest UTs. 4 | * 5 | * Jest Test cases should be isolated and if used for 6 | * VSCode extension specific tests, then it should 7 | * mock `vscode` module, for jest to work 8 | */ 9 | 10 | module.exports = { 11 | presets: [ 12 | ["@babel/preset-env", { targets: { node: "current" } }], 13 | "@babel/preset-typescript", 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /docs/customize-extension.md: -------------------------------------------------------------------------------- 1 | # Customize the Extension 2 | 3 | `phoenisx.cssvar` internally uses [PostCSS](https://github.com/postcss/postcss) to parse and 4 | find CSS variables. Use [PostCSS syntax parsers][syntax-list] to add support for your 5 | desired CSS syntax. 6 | 7 |
8 | 9 | ## Already supported source files 10 | 11 | The following list of source file extensions does not need any customization. 12 | `cssvar` already has in-built support for them. 13 | 14 | - CSS or CSS-like files: `css`, `scss`, `less`. 15 | - JS or JS-like files: `js`, `jsx`, `ts`, `tsx`. 16 | - Statically parses [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). No customization needed 17 | 18 | > `cssvar` has minimal support for parsing JS/TS files. 19 | > __*This won't be required, but in case you face issues with*__ 20 | > __*JS/TS source file parsing try*__ 21 | > __*using [postcss-css-in-js][css-in-js] parser,*__ 22 | > __*as described in the [following section](#example-ii)*__. 23 | > 24 | 25 | > **NOTE:** Customization might not always work, because the way these syntax parsers work are different 26 | > for different css extensions. Thus if you find an extension is not working properly, do raise an issue 27 | > and appreciate a PR for the same. 28 | 29 |

30 | 31 | ## Adding support for a new syntax 32 | 33 | > ***For customizations to work, you need to have 34 | [postcss](https://github.com/postcss/postcss) `v8+` installed in your local project's `node_modules`.*** 35 | 36 | First, install any syntax parser from the list provided [here][syntax-list] 37 | or from the list below. 38 | 39 | **List of PostCSS syntax libraries** 40 | - Syntax Parser for Sass source files: 41 | [postcss-sass][sass-syntax] 42 | - Syntax Parser for HTML source files: 43 | [postcss-html](https://github.com/gucong3000/postcss-html) 44 | - Syntax Parser for SugarSS source files: 45 | [sugarss](https://github.com/postcss/sugarss) 46 | - Syntax Parser for [Stylus](https://stylus-lang.com/) CSS extension: 47 | [postcss-styl](https://github.com/stylus/postcss-styl) 48 | - Syntax parser for `css-in-js` in JS/TS source files: 49 | [postcss-css-in-js][css-in-js] 50 | - Syntax parser for Lit templates in JS/TS source files: 51 | [postcss-lit](https://github.com/43081j/postcss-lit) 52 | 53 |
54 | 55 | ### Example I 56 | 57 | The following example helps demonstrate adding support for `sass` CSS Extension. 58 |
Install [postcss-sass][sass-syntax] package on your system: 59 | 60 | ```sh 61 | yarn add -D postcss postcss-sass 62 | ``` 63 | 64 | Once the above is done, edit your `cssvar` config to use this syntax: 65 | 66 | ```jsonc 67 | // .vscode/settings.json 68 | { 69 | "cssvar.postcssSyntax": { 70 | // [npm package name]: ["file extension list to use this syntax parser for"] 71 | "postcss-sass": ["sass"] 72 | } 73 | } 74 | ``` 75 | 76 |
77 | 78 | ### Example II 79 | 80 | The following example helps demonstrate adding support for `css-in-js` parsing support. 81 |
Install [postcss-css-in-js][css-in-js] and [postcss-syntax](https://github.com/gucong3000/postcss-syntax) packages 82 | on your system: 83 | 84 | ```sh 85 | yarn add -D postcss postcss-syntax @stylelint/postcss-css-in-js 86 | ``` 87 | 88 | Once the above is done, edit your `cssvar` config to use this syntax: 89 | 90 | ```jsonc 91 | // .vscode/settings.json 92 | { 93 | "cssvar.postcssSyntax": { 94 | "postcss-syntax": ["js", "jsx", "ts", "tsx"] 95 | } 96 | } 97 | ``` 98 | 99 | To see this setup working, uncomment this syntax in [js-parser example][js-parser-eg-line-link] 100 | 101 | 102 | [syntax-list]: https://github.com/postcss/postcss#syntaxes 103 | [sass-syntax]: https://github.com/AleshaOleg/postcss-sass 104 | [nested-plugin]: https://github.com/postcss/postcss-nested 105 | [css-in-js]: https://github.com/stylelint/postcss-css-in-js 106 | [js-parser-eg-line-link]: https://github.com/willofindie/vscode-cssvar/blob/main/examples/js-parser/.vscode/settings.json#L6 107 | -------------------------------------------------------------------------------- /docs/debug-extension.md: -------------------------------------------------------------------------------- 1 | # Debug extension 2 | 3 | ## Test the extension in isolation 4 | 5 | - Disable all extensions 6 | ![Disable all extensions](https://user-images.githubusercontent.com/11786283/188285728-bc301bd4-89de-476e-aca0-9cbbeb5010e7.png) 7 | 8 | - Enable just this extension: `phoenisx.cssvar` 9 | image 10 | 11 | - Test if the extension works properly after this, if not please follow the steps from next section 12 | - *You should enable all your installed extensions, once done testing the extension* 13 | ![Enable all extensions](https://user-images.githubusercontent.com/11786283/188285738-cc3dead9-2465-4690-8db1-c0c47bb2ab98.png) 14 | 15 | 16 | ## How to look into Console Errors/Warnings for this extension 17 | 18 | ### Step 1: 19 | 20 | Open your deveoper tools form VSCode `Help` menu: 21 | 22 | toggle_dev_tools 27 | 28 | ### Step 2: 29 | 30 | Find the error/warning in the VSCode devtools view. 31 | The error will have the following structure: 32 | 33 | ```yml 34 | [cssvar]: ERROR MESSAGE 35 | ``` 36 | 37 | > Add the filter `[cssvar]:` in the DevTools Console, for better visibility. 38 | 39 | Please share the entire error message when opening any issue. :bow: 40 | -------------------------------------------------------------------------------- /docs/theming.md: -------------------------------------------------------------------------------- 1 | # CSS Theming 2 | 3 | > NOTE: Theming in CSS can be complicated, and I am still figuring out how to properly support themed variables, so that duplication in Autocomplete list is as minimal as possible. In future, I am planning to hide unnessary variables from the list, defined by user, completely. This will help scoping variables so that developers do not use variables which are not supposed to be used or tweaked. 4 | > 5 | > This feature can be benefitted in adding a liniting support for used CSS variables. 6 | 7 | Theming in any project can be implemented in various ways. This plugin will support two ways of 8 | theming, since I feel they are the most suitable ways of theming using CSS variables. 9 | 10 | - Using Classes (Supported in extension) 11 | - Using Color Schemes (Not supported yet) 12 | 13 | 14 | ## Theming using CSS classes 15 | 16 | ```css 17 | :root { 18 | /* Provide Generic variables here */ 19 | --color-gray-50: #FAFAFA; 20 | --color-gray-100: #F5F5F5; 21 | --color-gray-200: #EEEEEE; 22 | --color-gray-300: #E0E0E0; 23 | --color-gray-800: #424242; 24 | --color-gray-900: #212121; 25 | --color-purple-100: #D1C4E9; 26 | --color-purple-200: #B39DDB; 27 | --color-purple-500: #6200EE; 28 | --color-purple-700: #3700B3; 29 | } 30 | 31 | body { 32 | --text: var(--color-gray-900); 33 | --disabled-text: var(--color-gray-300); 34 | --primary: var(--color-purple-500); 35 | --primary-dark: var(--color-purple-700); 36 | } 37 | 38 | body.dark { 39 | --text: var(--color-gray-50); 40 | --disabled-text: var(--color-gray-200); 41 | --primary: var(--color-purple-100); 42 | --primary-dark: var(--color-purple-200); 43 | } 44 | 45 | 46 | .usage-btn { 47 | /* Will switch when parent body class changes to .dark */ 48 | color: var(--text); 49 | background: var(--primary); 50 | } 51 | ``` 52 | 53 | Support for this style of theming is present in current version of this extension. 54 | Enabling theme support in Extension helps removing duplicate CSS variables from the 55 | Autocomplete list, making sure the list doesn't grow unnecessarily. 56 | 57 | ### Extension Config 58 | 59 | Following feature is still improving, and will change in future. Use the below config, if you have 60 | too many duplicates in your autocomplete list, due to theming. 61 | 62 | ```jsonc 63 | { 64 | /* Let Extension know there exists a `.dark` theme */ 65 | "cssvar.themes": ["dark", "dim"], 66 | /* If the below config is true, css variables from `.dark` and `.dim` theme will be hidden from list */ 67 | "cssvar.excludeThemedVariables": true 68 | } 69 | ``` 70 | 71 | 72 | ## Theming using CSS `color-scheme` property 73 | 74 | Read the article here: [Building a Color Scheme](https://web.dev/building-a-color-scheme/) 75 | This is a more robust way of theming Website using CSS variables, but support for this theming 76 | will come in a future release. 77 | 78 | ```html 79 | 80 | 81 | 82 | ``` 83 | 84 | ```css 85 | :root { 86 | --brand-hue: 200; 87 | --brand-saturation: 100%; 88 | --brand-lightness: 50%; 89 | } 90 | 91 | /** 92 | * Defines the auto scheme, i.e. when html does not has any 93 | * `color-scheme` attribute set. 94 | */ 95 | :root { 96 | color-scheme: light; 97 | --brand: hsl( 98 | var(--brand-hue) 99 | var(--brand-saturation) 100 | var(--brand-lightness) 101 | ); 102 | --text1: hsl(var(--brand-hue) var(--brand-saturation) 10%); 103 | } 104 | 105 | /** 106 | * Following media query helps take in OS's default color scheme 107 | * i.e. on Mac if the theme is set to Dark, the browser will enable the 108 | * following color scheme, overriding the light ones. 109 | */ 110 | @media (prefers-color-scheme: dark) { 111 | :root { 112 | color-scheme: dark; 113 | --brand: hsl( 114 | var(--brand-hue) 115 | calc(var(--brand-saturation) / 2) 116 | calc(var(--brand-lightness) / 1.5) 117 | ); 118 | --text1: hsl(var(--brand-hue) 15% 85%); 119 | } 120 | } 121 | 122 | [color-scheme="dark"] { 123 | color-scheme: dark; 124 | --brand: hsl( 125 | var(--brand-hue) 126 | calc(var(--brand-saturation) / 2) 127 | calc(var(--brand-lightness) / 1.5) 128 | ); 129 | --text1: hsl(var(--brand-hue) 15% 85%); 130 | } 131 | 132 | [color-scheme="dim"] { 133 | color-scheme: dark; 134 | --brand: hsl( 135 | var(--brand-hue) 136 | calc(var(--brand-saturation) / 1.25) 137 | calc(var(--brand-lightness) / 1.25) 138 | ); 139 | --text1: hsl(var(--brand-hue) 15% 75%); 140 | } 141 | ``` 142 | -------------------------------------------------------------------------------- /esbuild.sh: -------------------------------------------------------------------------------- 1 | npx esbuild ./src/extension.ts \ 2 | --external:vscode \ 3 | --external:"@tokencss/postcss" \ 4 | --format=cjs \ 5 | --platform=node \ 6 | --target=node16 \ 7 | --outfile=out/extension.js \ 8 | --bundle --minify --define:process.env.NODE_ENV='"production"' 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import globals from "globals"; 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import prettierLintRecommended from "eslint-plugin-prettier/recommended"; 6 | import jest from "eslint-plugin-jest"; 7 | 8 | /** 9 | * @typedef {import('@typescript-eslint/utils').TSESLint.FlatConfig.Config["rules"]} ESLintRules 10 | * @typedef {import('@typescript-eslint/utils').TSESLint.FlatConfig.Config["languageOptions"]} LanguageOptions 11 | */ 12 | 13 | /** @type {ESLintRules} */ 14 | const BASE_RULES = { 15 | "@typescript-eslint/camelcase": 0, 16 | "@typescript-eslint/explicit-function-return-type": 0, 17 | "@typescript-eslint/explicit-module-boundary-types": 0, 18 | "@typescript-eslint/no-explicit-any": 0, 19 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 20 | "@typescript-eslint/no-var-requires": 0, 21 | "@typescript-eslint/ban-ts-comment": 1, 22 | "prettier/prettier": "error", 23 | "no-console": "error", 24 | }; 25 | 26 | /** @type {LanguageOptions} */ 27 | const BASE_LANGUAGE_OPTIONS = { 28 | ecmaVersion: 2022, 29 | sourceType: "module", 30 | globals: { 31 | ...globals.browser, 32 | ...globals.node, 33 | }, 34 | }; 35 | 36 | export default tseslint.config( 37 | eslint.configs.recommended, 38 | prettierLintRecommended, 39 | ...tseslint.configs.recommended, 40 | { 41 | files: ["src/**/*.ts"], 42 | ignores: ["out/", "src/test/", "src/scripts/"], 43 | languageOptions: { 44 | ...BASE_LANGUAGE_OPTIONS, 45 | }, 46 | rules: BASE_RULES, 47 | }, 48 | { 49 | files: ["src/test/**/*.ts"], 50 | ignores: ["out/", "src/scripts/"], 51 | ...jest.configs["flat/recommended"], 52 | languageOptions: { 53 | ...BASE_LANGUAGE_OPTIONS, 54 | }, 55 | rules: { 56 | ...jest.configs["flat/recommended"].rules, 57 | ...BASE_RULES, 58 | }, 59 | } 60 | ); 61 | -------------------------------------------------------------------------------- /examples/basics/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[css]" : { 3 | "editor.suggest.showVariables": true 4 | }, 5 | "cssvar.disableSort": true 6 | } 7 | -------------------------------------------------------------------------------- /examples/basics/README.md: -------------------------------------------------------------------------------- 1 | # Brief 2 | 3 | This extension works flawlessly with pure CSS projects. 4 | No settings required for pure CSS projects, but you can customize the settings if needed. 5 | 6 | **NOTE ::** 7 | By default, when not settings is provided, this extension will scan all CSS files in current project. 8 | This is fine for smaller projects, but is not recommended for larger projects, since scanning and parsing 9 | a huge list of css files can make the extension slow and might not be desirable. 10 | 11 | Thus, it is recommended to at-least set `cssvar.files` settings for your projects. 12 | -------------------------------------------------------------------------------- /examples/basics/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --size-sm: 12px; 3 | --size-md: 18px; 4 | --size-lg: 2rem; 5 | 6 | --color-red-50: #ffebee; 7 | --color-red-100: rgb(255, 205, 210); 8 | --color-purple-300: hsl(291.25, 46.6%, 59.61%); 9 | --color-purple-500-alpha: hsla(277.32, 70.17%, 35.49%, 0.67); 10 | 11 | --color-1: #78BA50; 12 | 13 | --gray-1: #111; 14 | --gray-2: #121212; 15 | --gray-3: #222; 16 | --gray-4: #232323; 17 | --gray-5: #333; 18 | --gray-10: #999; 19 | --gray-11: #9c9c9c; 20 | } 21 | 22 | :body { 23 | --size-sm: 12px; 24 | --size-md: 18px; 25 | --size-lg: 2rem; 26 | 27 | --color-red-50: #ffebee; 28 | --color-red-100: rgb(255, 205, 210); 29 | --color-purple-300: hsl(291.25, 46.6%, 59.61%); 30 | --color-purple-500-alpha: hsla(277.32, 70.17%, 35.49%, 0.67); 31 | } 32 | -------------------------------------------------------------------------------- /examples/basics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Testing CSS Variables 7 | 8 | 9 | 10 | 11 |
12 |

Testing CSS Variables

13 |

14 | Commodo esse enim officia eu aute non consequat eu ad occaecat. Elit adipisicing et dolore cillum dolore et irure 15 | ullamco sint irure Lorem consequat nisi duis. Ullamco ipsum aute sit laboris minim ex pariatur fugiat adipisicing 16 | cillum. 17 |

18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/basics/local.css: -------------------------------------------------------------------------------- 1 | @import url("./global.css"); 2 | @import url("./theme.css"); 3 | 4 | :root { 5 | --color-default-bg: #A6B9CA; 6 | --color-default-fg: #223548; 7 | } 8 | 9 | body { 10 | color: var(--color-default-fg); 11 | background-color: var(--color-purple-500-alpha); 12 | } 13 | 14 | .container { 15 | width: var(--container-size); 16 | padding: var(--container-padding); 17 | background-color: var(--test-custom-selector); 18 | color: var(--gray-1); 19 | } 20 | -------------------------------------------------------------------------------- /examples/basics/spacing.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Following two variables will change respective to media-queries */ 3 | --container-size: calc(100% - 1rem); 4 | --container-padding: 0.5rem; 5 | } 6 | 7 | @media screen and (min-width: 1020px) { 8 | :root { 9 | --container-size: 960; 10 | --container-padding: 1rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/basics/theme.css: -------------------------------------------------------------------------------- 1 | @import url("./global.css"); 2 | 3 | /* Nested variables */ 4 | :root { 5 | /* Following two variables will change respective to media-queries */ 6 | --container-size: 100%; 7 | --container-padding: 0.5rem; 8 | } 9 | 10 | @media screen and (min-width: 960px) { 11 | :root { 12 | --container-size: 900px; 13 | --container-padding: 1rem; 14 | } 15 | } 16 | 17 | /* CSS with new at-rules */ 18 | 19 | @custom-selector :--root :root, :after, :before; 20 | 21 | @layer variables { 22 | @layer colors { 23 | :--root { 24 | --test-custom-selector: rgba(17, 17, 17, 1); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/css-extensions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["**/*.css", "**/*.scss"], 3 | } 4 | -------------------------------------------------------------------------------- /examples/css-extensions/README.md: -------------------------------------------------------------------------------- 1 | # Brief 2 | 3 | In case you are not directly working CSS files and using CSS extensions like SASS, SCSS, LESS, POSTCSS 4 | you can still use this extension. 5 | 6 | CSS extensions can have some specific syntax that are not compatible with pure CSS specs, like 7 | SASS interpolations, single line comments and more. 8 | 9 | This extension will work fine without any customization unless you are using non-standard CSS syntax. 10 | 11 | To support non-standard CSS syntax, this extension uses `cssvar.postcssSyntax`. 12 | This example demonstrates the use of that setting. 13 | 14 | Details can be read [here for Customization](../../customize-extension.md). 15 | -------------------------------------------------------------------------------- /examples/css-extensions/index.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: var(--space-1) var(--space-2); 3 | background-color: var(--brand-2); 4 | } 5 | -------------------------------------------------------------------------------- /examples/css-extensions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-extensions", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /examples/css-extensions/styles.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --space-1: 1rem; 3 | --space-2: 2rem; 4 | } 5 | 6 | .app { 7 | // Nested rules are not supported in CSS 8 | .child { 9 | color: var(--brand-2); 10 | background-color: var(--brand-3); 11 | } 12 | 13 | width: var(--space-1); 14 | height: var(--space-1); 15 | } 16 | -------------------------------------------------------------------------------- /examples/css-extensions/theme.scss: -------------------------------------------------------------------------------- 1 | // Single line comments is not supported in CSS. 2 | $color-1: #FF8A65; 3 | $color-2: #8D6E63; 4 | $color-3: #BDBDBD; 5 | 6 | :root { 7 | --brand-1: #{$color-1}; 8 | --brand-2: #{$color-2}; 9 | --brand-3: #{$color-3}; 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/css-imports/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": [ 3 | "*.css", 4 | "https://unpkg.com/open-props@1.4.14/violet.min.css", 5 | "https://unpkg.com/plasttic-reset@2.0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/css-imports/index.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The extension will cache all remote url imports, irrespective of them having 3 | * any CSS variables or not. The files are stored in `$TMPDIR/cssvar` temp folder 4 | * if you ever want to manually remove them. These files will automatically be 5 | * cleaned up by your OS, if not used for a specific period of time or after a reboot. 6 | */ 7 | @import "https://cdn.jsdelivr.net/npm/modern-normalize@1.1.0/modern-normalize.min.css"; 8 | @import './local.css'; 9 | 10 | /* Following is very specific to webpack/react projects */ 11 | /* I won't be supporting this in this extension, as a user can import */ 12 | /* such css files from extension settings */ 13 | /* But's it's wise to ignore such imports */ 14 | @import '~open-props/open-props.min.css'; 15 | @import 'modern-normalize/modern-normalize.css'; 16 | 17 | @import url('node_modules/open-props/red.min.css') layer(utilities) print, screen; 18 | 19 | /* Following import syntax is supported by vite projects */ 20 | /* Again this is very bundler specific and will not be supported */ 21 | /* @import 'pollen-css/pollen.css'; */ 22 | 23 | @import url(https://unpkg.com/pollen-css@4.4.0/pollen.css); 24 | @import "https://unpkg.com/pollen-css"; 25 | 26 | body { 27 | color: var(--red-2); 28 | width: var(--width-sm); 29 | padding: var(--gap-md); 30 | border-color: var(--color-red); 31 | grid-template-columns: var(--grid-page); 32 | background-color: var(--c-blue); 33 | border-top-color: var(--orange-2); 34 | box-shadow: 0 0 3px 0 var(--violet-2); 35 | margin: var(--size-10); 36 | } 37 | -------------------------------------------------------------------------------- /examples/css-imports/local.css: -------------------------------------------------------------------------------- 1 | @import url("https://unpkg.com/open-props@1.4.14/orange.min.css"); 2 | @import url(./nested.css); 3 | 4 | @layer x01, x02; 5 | @layer x01 { 6 | .test { 7 | --test-x01: green; 8 | } 9 | } 10 | @layer x02 { 11 | .test { 12 | --test-x01: red; 13 | } 14 | } 15 | 16 | :root { 17 | --gap-sm: 0.25rem; 18 | --gap-md: 0.5rem; 19 | --gap-lg: 1rem; 20 | --gap-xl: 2rem; 21 | --gap-xxl: 4rem; 22 | } 23 | -------------------------------------------------------------------------------- /examples/css-imports/nested.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --c-red: red; 3 | --c-green: green; 4 | --c-blue: blue; 5 | } 6 | -------------------------------------------------------------------------------- /examples/css-imports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-imports", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "open-props": "^1.4.14", 14 | "pollen-css": "^4.4.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/css-imports/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | open-props: ^1.4.14 5 | pollen-css: ^4.4.0 6 | 7 | dependencies: 8 | open-props: 1.4.14 9 | pollen-css: 4.4.0 10 | 11 | packages: 12 | 13 | /@babel/code-frame/7.18.6: 14 | resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} 15 | engines: {node: '>=6.9.0'} 16 | dependencies: 17 | '@babel/highlight': 7.18.6 18 | dev: false 19 | 20 | /@babel/helper-validator-identifier/7.18.6: 21 | resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} 22 | engines: {node: '>=6.9.0'} 23 | dev: false 24 | 25 | /@babel/highlight/7.18.6: 26 | resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} 27 | engines: {node: '>=6.9.0'} 28 | dependencies: 29 | '@babel/helper-validator-identifier': 7.18.6 30 | chalk: 2.4.2 31 | js-tokens: 4.0.0 32 | dev: false 33 | 34 | /@types/parse-json/4.0.0: 35 | resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} 36 | dev: false 37 | 38 | /ansi-styles/3.2.1: 39 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 40 | engines: {node: '>=4'} 41 | dependencies: 42 | color-convert: 1.9.3 43 | dev: false 44 | 45 | /balanced-match/1.0.2: 46 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 47 | dev: false 48 | 49 | /callsites/3.1.0: 50 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 51 | engines: {node: '>=6'} 52 | dev: false 53 | 54 | /case/1.6.3: 55 | resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} 56 | engines: {node: '>= 0.8.0'} 57 | dev: false 58 | 59 | /chalk/2.4.2: 60 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 61 | engines: {node: '>=4'} 62 | dependencies: 63 | ansi-styles: 3.2.1 64 | escape-string-regexp: 1.0.5 65 | supports-color: 5.5.0 66 | dev: false 67 | 68 | /color-convert/1.9.3: 69 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 70 | dependencies: 71 | color-name: 1.1.3 72 | dev: false 73 | 74 | /color-name/1.1.3: 75 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 76 | dev: false 77 | 78 | /commander/9.4.0: 79 | resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} 80 | engines: {node: ^12.20.0 || >=14} 81 | dev: false 82 | 83 | /cosmiconfig/7.0.1: 84 | resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} 85 | engines: {node: '>=10'} 86 | dependencies: 87 | '@types/parse-json': 4.0.0 88 | import-fresh: 3.3.0 89 | parse-json: 5.2.0 90 | path-type: 4.0.0 91 | yaml: 1.10.2 92 | dev: false 93 | 94 | /css-vars-ponyfill/2.4.8: 95 | resolution: {integrity: sha512-4/j4AX4htytYHWyHVZ2BFQ+NoCGZEcOH2h4/2mmgE4SkrFg4Xq6tGYR77DtvvUIDsaXuJN+sj41bbgauA0Gfmg==} 96 | dependencies: 97 | balanced-match: 1.0.2 98 | get-css-data: 2.1.0 99 | dev: false 100 | 101 | /error-ex/1.3.2: 102 | resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} 103 | dependencies: 104 | is-arrayish: 0.2.1 105 | dev: false 106 | 107 | /escape-string-regexp/1.0.5: 108 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 109 | engines: {node: '>=0.8.0'} 110 | dev: false 111 | 112 | /get-css-data/2.1.0: 113 | resolution: {integrity: sha512-HtPrzGk8aBF9rLeQNuImcXci7YVqsMEKzVflEWaCJu25ehxyDNiZRWoSxqSFUBfma8LERqKo70t/TcaGjIsM9g==} 114 | dev: false 115 | 116 | /has-flag/3.0.0: 117 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 118 | engines: {node: '>=4'} 119 | dev: false 120 | 121 | /import-fresh/3.3.0: 122 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 123 | engines: {node: '>=6'} 124 | dependencies: 125 | parent-module: 1.0.1 126 | resolve-from: 4.0.0 127 | dev: false 128 | 129 | /is-arrayish/0.2.1: 130 | resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} 131 | dev: false 132 | 133 | /javascript-stringify/2.1.0: 134 | resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} 135 | dev: false 136 | 137 | /js-tokens/4.0.0: 138 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 139 | dev: false 140 | 141 | /json-parse-even-better-errors/2.3.1: 142 | resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} 143 | dev: false 144 | 145 | /lines-and-columns/1.2.4: 146 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 147 | dev: false 148 | 149 | /map-obj/5.0.2: 150 | resolution: {integrity: sha512-K6K2NgKnTXimT3779/4KxSvobxOtMmx1LBZ3NwRxT/MDIR3Br/fQ4Q+WCX5QxjyUR8zg5+RV9Tbf2c5pAWTD2A==} 151 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 152 | dev: false 153 | 154 | /open-props/1.4.14: 155 | resolution: {integrity: sha512-EkTUczMzty8mIvnWGpC41ELIejARiuljsXTEz7YDRKJn5dJ1/njsLTpNhn2rsM3JSW95p4G94ojsgzxbaiFmNA==} 156 | dev: false 157 | 158 | /parent-module/1.0.1: 159 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 160 | engines: {node: '>=6'} 161 | dependencies: 162 | callsites: 3.1.0 163 | dev: false 164 | 165 | /parse-json/5.2.0: 166 | resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} 167 | engines: {node: '>=8'} 168 | dependencies: 169 | '@babel/code-frame': 7.18.6 170 | error-ex: 1.3.2 171 | json-parse-even-better-errors: 2.3.1 172 | lines-and-columns: 1.2.4 173 | dev: false 174 | 175 | /path-type/4.0.0: 176 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 177 | engines: {node: '>=8'} 178 | dev: false 179 | 180 | /pollen-css/4.4.0: 181 | resolution: {integrity: sha512-BVmTY0/o/WoMCk8rmHBrEYA5bffuehsIAkf1obOq+wzcnwvorNCbKhRFqyTev4RI5+RHOhZMjMe063tEjK8LWg==} 182 | hasBin: true 183 | dependencies: 184 | case: 1.6.3 185 | commander: 9.4.0 186 | cosmiconfig: 7.0.1 187 | css-vars-ponyfill: 2.4.8 188 | javascript-stringify: 2.1.0 189 | map-obj: 5.0.2 190 | prettier: 2.7.1 191 | dev: false 192 | 193 | /prettier/2.7.1: 194 | resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} 195 | engines: {node: '>=10.13.0'} 196 | hasBin: true 197 | dev: false 198 | 199 | /resolve-from/4.0.0: 200 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 201 | engines: {node: '>=4'} 202 | dev: false 203 | 204 | /supports-color/5.5.0: 205 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 206 | engines: {node: '>=4'} 207 | dependencies: 208 | has-flag: 3.0.0 209 | dev: false 210 | 211 | /yaml/1.10.2: 212 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 213 | engines: {node: '>= 6'} 214 | dev: false 215 | -------------------------------------------------------------------------------- /examples/css-in-node-modules/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Since we want to scan css files inside `node_modules as well 3 | // we need to set cssvar.ignore property to empty array 4 | "cssvar.ignore": [], 5 | 6 | // Scann all .css files, which is the default 7 | // "cssvar.files": [ "**/*.css" ] 8 | 9 | // Uncomment the following if you only need to scan Pollen css files 10 | // "cssvar.files": [ "./node_modules/pollen-css/pollen.css" ] 11 | 12 | // Uncomment the following if you only want to scan Bootstrap css files 13 | // "cssvar.files": [ "node_modules/bootstrap/dist/css/bootstrap.css" ] 14 | 15 | // Uncomment the following if you only want to scan Open Props css files 16 | // "cssvar.files": [ "node_modules/open-props/open-props.min.css" ] 17 | 18 | // Supporting only required css files from node_modules 19 | "cssvar.files": [ 20 | "node_modules/pollen-css/pollen.css", 21 | "node_modules/bootstrap/dist/css/bootstrap.css", 22 | "node_modules/open-props/open-props.min.css" 23 | ], 24 | "cssvar.disableSort": true 25 | } 26 | -------------------------------------------------------------------------------- /examples/css-in-node-modules/index.css: -------------------------------------------------------------------------------- 1 | /* Variables from Bootstrap package */ 2 | .bootstrap { 3 | color: var(--bs-code-color); 4 | } 5 | 6 | /* Variables from Open Props package */ 7 | .open-props { 8 | border-radius: var(--radius-100); 9 | border-color: var(--red-5); 10 | border-width: var(--size-2); 11 | } 12 | 13 | /* Varibles from Pollen package */ 14 | .pollen { 15 | color: var(--color-blue-300); 16 | } 17 | -------------------------------------------------------------------------------- /examples/css-in-node-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-in-node-modules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bootstrap": "^5.2.0", 14 | "open-props": "^1.4.4", 15 | "pollen-css": "^4.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/css-in-node-modules/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | bootstrap: ^5.2.0 5 | open-props: ^1.4.4 6 | pollen-css: ^4.3.1 7 | 8 | dependencies: 9 | bootstrap: 5.2.0 10 | open-props: 1.4.4 11 | pollen-css: 4.3.1 12 | 13 | packages: 14 | 15 | /@babel/code-frame/7.18.6: 16 | resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} 17 | engines: {node: '>=6.9.0'} 18 | dependencies: 19 | '@babel/highlight': 7.18.6 20 | dev: false 21 | 22 | /@babel/helper-validator-identifier/7.18.6: 23 | resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} 24 | engines: {node: '>=6.9.0'} 25 | dev: false 26 | 27 | /@babel/highlight/7.18.6: 28 | resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} 29 | engines: {node: '>=6.9.0'} 30 | dependencies: 31 | '@babel/helper-validator-identifier': 7.18.6 32 | chalk: 2.4.2 33 | js-tokens: 4.0.0 34 | dev: false 35 | 36 | /@types/parse-json/4.0.0: 37 | resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} 38 | dev: false 39 | 40 | /ansi-styles/3.2.1: 41 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 42 | engines: {node: '>=4'} 43 | dependencies: 44 | color-convert: 1.9.3 45 | dev: false 46 | 47 | /balanced-match/1.0.2: 48 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 49 | dev: false 50 | 51 | /bootstrap/5.2.0: 52 | resolution: {integrity: sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==} 53 | peerDependencies: 54 | '@popperjs/core': ^2.11.5 55 | dev: false 56 | 57 | /callsites/3.1.0: 58 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 59 | engines: {node: '>=6'} 60 | dev: false 61 | 62 | /case/1.6.3: 63 | resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} 64 | engines: {node: '>= 0.8.0'} 65 | dev: false 66 | 67 | /chalk/2.4.2: 68 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 69 | engines: {node: '>=4'} 70 | dependencies: 71 | ansi-styles: 3.2.1 72 | escape-string-regexp: 1.0.5 73 | supports-color: 5.5.0 74 | dev: false 75 | 76 | /color-convert/1.9.3: 77 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 78 | dependencies: 79 | color-name: 1.1.3 80 | dev: false 81 | 82 | /color-name/1.1.3: 83 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 84 | dev: false 85 | 86 | /commander/9.4.0: 87 | resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} 88 | engines: {node: ^12.20.0 || >=14} 89 | dev: false 90 | 91 | /cosmiconfig/7.0.1: 92 | resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} 93 | engines: {node: '>=10'} 94 | dependencies: 95 | '@types/parse-json': 4.0.0 96 | import-fresh: 3.3.0 97 | parse-json: 5.2.0 98 | path-type: 4.0.0 99 | yaml: 1.10.2 100 | dev: false 101 | 102 | /css-vars-ponyfill/2.4.7: 103 | resolution: {integrity: sha512-KhG3AbiZrUpIvAQ9Oc/iBqCitmXg6MajFqNRQd9nHvlwOo8p54HTq5DFCIaAUwMGRyttJ+mBmZCRSHJpe6J9cg==} 104 | dependencies: 105 | balanced-match: 1.0.2 106 | get-css-data: 2.1.0 107 | dev: false 108 | 109 | /error-ex/1.3.2: 110 | resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} 111 | dependencies: 112 | is-arrayish: 0.2.1 113 | dev: false 114 | 115 | /escape-string-regexp/1.0.5: 116 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 117 | engines: {node: '>=0.8.0'} 118 | dev: false 119 | 120 | /get-css-data/2.1.0: 121 | resolution: {integrity: sha512-HtPrzGk8aBF9rLeQNuImcXci7YVqsMEKzVflEWaCJu25ehxyDNiZRWoSxqSFUBfma8LERqKo70t/TcaGjIsM9g==} 122 | dev: false 123 | 124 | /has-flag/3.0.0: 125 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 126 | engines: {node: '>=4'} 127 | dev: false 128 | 129 | /import-fresh/3.3.0: 130 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 131 | engines: {node: '>=6'} 132 | dependencies: 133 | parent-module: 1.0.1 134 | resolve-from: 4.0.0 135 | dev: false 136 | 137 | /is-arrayish/0.2.1: 138 | resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} 139 | dev: false 140 | 141 | /javascript-stringify/2.1.0: 142 | resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} 143 | dev: false 144 | 145 | /js-tokens/4.0.0: 146 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 147 | dev: false 148 | 149 | /json-parse-even-better-errors/2.3.1: 150 | resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} 151 | dev: false 152 | 153 | /lines-and-columns/1.2.4: 154 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 155 | dev: false 156 | 157 | /map-obj/5.0.2: 158 | resolution: {integrity: sha512-K6K2NgKnTXimT3779/4KxSvobxOtMmx1LBZ3NwRxT/MDIR3Br/fQ4Q+WCX5QxjyUR8zg5+RV9Tbf2c5pAWTD2A==} 159 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 160 | dev: false 161 | 162 | /open-props/1.4.4: 163 | resolution: {integrity: sha512-/B72p2glETAySGy22qsbWU6FF34R7bW/3RckuAE3GFL0siDa+lT+6AdTEvvxceR5Cke/MKMWtKHe7LpY6w+/9A==} 164 | dev: false 165 | 166 | /parent-module/1.0.1: 167 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 168 | engines: {node: '>=6'} 169 | dependencies: 170 | callsites: 3.1.0 171 | dev: false 172 | 173 | /parse-json/5.2.0: 174 | resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} 175 | engines: {node: '>=8'} 176 | dependencies: 177 | '@babel/code-frame': 7.18.6 178 | error-ex: 1.3.2 179 | json-parse-even-better-errors: 2.3.1 180 | lines-and-columns: 1.2.4 181 | dev: false 182 | 183 | /path-type/4.0.0: 184 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 185 | engines: {node: '>=8'} 186 | dev: false 187 | 188 | /pollen-css/4.3.1: 189 | resolution: {integrity: sha512-3FtOkHADMlae6FhfpOEIM1zgsTiaSv21e2RhqhTD9sSuZ3ZFigGVBoaGgOrg7DMenW8ZUwcb5DWhl2d2xldbfQ==} 190 | hasBin: true 191 | dependencies: 192 | case: 1.6.3 193 | commander: 9.4.0 194 | cosmiconfig: 7.0.1 195 | css-vars-ponyfill: 2.4.7 196 | javascript-stringify: 2.1.0 197 | map-obj: 5.0.2 198 | dev: false 199 | 200 | /resolve-from/4.0.0: 201 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 202 | engines: {node: '>=4'} 203 | dev: false 204 | 205 | /supports-color/5.5.0: 206 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 207 | engines: {node: '>=4'} 208 | dependencies: 209 | has-flag: 3.0.0 210 | dev: false 211 | 212 | /yaml/1.10.2: 213 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 214 | engines: {node: '>= 6'} 215 | dev: false 216 | -------------------------------------------------------------------------------- /examples/custom-extension/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["*.styl"], 3 | "cssvar.extensions": ["styl"], 4 | "cssvar.postcssSyntax": { 5 | // [npm package name]: ["file extension list to use this syntax parser for"] 6 | "postcss-styl": ["styl"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/custom-extension/README.md: -------------------------------------------------------------------------------- 1 | ## Stylus CSS extension 2 | 3 | This extension uses and creates a very different AST using postcss, which is incompatible with 4 | `cssvar` vscode extension. 5 | 6 | To fully support this CSS extension, I will have to either provide in-built support for this 7 | CSS extension or ignore it for now. 8 | I am ignoring it for now. 9 | -------------------------------------------------------------------------------- /examples/custom-extension/index.styl: -------------------------------------------------------------------------------- 1 | border-radius() 2 | -webkit-border-radius: arguments 3 | -moz-border-radius: arguments 4 | border-radius: arguments 5 | 6 | body 7 | font: 12px Helvetica, Arial, sans-serif 8 | color: --c 9 | 10 | a.button 11 | border-radius: 5px 12 | -------------------------------------------------------------------------------- /examples/custom-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-extension", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "postcss": "^8.4.21", 14 | "postcss-styl": "^0.12.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/custom-extension/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | postcss: ^8.4.21 5 | postcss-styl: ^0.12.3 6 | 7 | devDependencies: 8 | postcss: 8.4.21 9 | postcss-styl: 0.12.3 10 | 11 | packages: 12 | 13 | /atob/2.1.2: 14 | resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} 15 | engines: {node: '>= 4.5.0'} 16 | hasBin: true 17 | dev: true 18 | 19 | /balanced-match/1.0.2: 20 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 21 | dev: true 22 | 23 | /brace-expansion/1.1.11: 24 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 25 | dependencies: 26 | balanced-match: 1.0.2 27 | concat-map: 0.0.1 28 | dev: true 29 | 30 | /concat-map/0.0.1: 31 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 32 | dev: true 33 | 34 | /css/3.0.0: 35 | resolution: {integrity: sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==} 36 | dependencies: 37 | inherits: 2.0.4 38 | source-map: 0.6.1 39 | source-map-resolve: 0.6.0 40 | dev: true 41 | 42 | /debug/4.3.4: 43 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 44 | engines: {node: '>=6.0'} 45 | peerDependencies: 46 | supports-color: '*' 47 | peerDependenciesMeta: 48 | supports-color: 49 | optional: true 50 | dependencies: 51 | ms: 2.1.2 52 | dev: true 53 | 54 | /decode-uri-component/0.2.2: 55 | resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} 56 | engines: {node: '>=0.10'} 57 | dev: true 58 | 59 | /fast-diff/1.2.0: 60 | resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} 61 | dev: true 62 | 63 | /fs.realpath/1.0.0: 64 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 65 | dev: true 66 | 67 | /glob/7.2.3: 68 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 69 | dependencies: 70 | fs.realpath: 1.0.0 71 | inflight: 1.0.6 72 | inherits: 2.0.4 73 | minimatch: 3.1.2 74 | once: 1.4.0 75 | path-is-absolute: 1.0.1 76 | dev: true 77 | 78 | /inflight/1.0.6: 79 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 80 | dependencies: 81 | once: 1.4.0 82 | wrappy: 1.0.2 83 | dev: true 84 | 85 | /inherits/2.0.4: 86 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 87 | dev: true 88 | 89 | /lodash.sortedlastindex/4.1.0: 90 | resolution: {integrity: sha512-s8xEQdsp2Tu5zUqVdFSe9C0kR8YlnAJYLqMdkh+pIRBRxF6/apWseLdHl3/+jv2I61dhPwtI/Ff+EqvCpc+N8w==} 91 | dev: true 92 | 93 | /minimatch/3.1.2: 94 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 95 | dependencies: 96 | brace-expansion: 1.1.11 97 | dev: true 98 | 99 | /ms/2.1.2: 100 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 101 | dev: true 102 | 103 | /nanoid/3.3.4: 104 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} 105 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 106 | hasBin: true 107 | dev: true 108 | 109 | /once/1.4.0: 110 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 111 | dependencies: 112 | wrappy: 1.0.2 113 | dev: true 114 | 115 | /path-is-absolute/1.0.1: 116 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 117 | engines: {node: '>=0.10.0'} 118 | dev: true 119 | 120 | /picocolors/1.0.0: 121 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 122 | dev: true 123 | 124 | /postcss-styl/0.12.3: 125 | resolution: {integrity: sha512-8I7Cd8sxiEITIp32xBK4K/Aj1ukX6vuWnx8oY/oAH35NfQI4OZaY5nd68Yx8HeN5S49uhQ6DL0rNk0ZBu/TaLg==} 126 | engines: {node: ^8.10.0 || ^10.13.0 || ^11.10.1 || >=12.13.0} 127 | dependencies: 128 | debug: 4.3.4 129 | fast-diff: 1.2.0 130 | lodash.sortedlastindex: 4.1.0 131 | postcss: 8.4.21 132 | stylus: 0.57.0 133 | transitivePeerDependencies: 134 | - supports-color 135 | dev: true 136 | 137 | /postcss/8.4.21: 138 | resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} 139 | engines: {node: ^10 || ^12 || >=14} 140 | dependencies: 141 | nanoid: 3.3.4 142 | picocolors: 1.0.0 143 | source-map-js: 1.0.2 144 | dev: true 145 | 146 | /safer-buffer/2.1.2: 147 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 148 | dev: true 149 | 150 | /sax/1.2.4: 151 | resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} 152 | dev: true 153 | 154 | /source-map-js/1.0.2: 155 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 156 | engines: {node: '>=0.10.0'} 157 | dev: true 158 | 159 | /source-map-resolve/0.6.0: 160 | resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} 161 | deprecated: See https://github.com/lydell/source-map-resolve#deprecated 162 | dependencies: 163 | atob: 2.1.2 164 | decode-uri-component: 0.2.2 165 | dev: true 166 | 167 | /source-map/0.6.1: 168 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 169 | engines: {node: '>=0.10.0'} 170 | dev: true 171 | 172 | /source-map/0.7.4: 173 | resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} 174 | engines: {node: '>= 8'} 175 | dev: true 176 | 177 | /stylus/0.57.0: 178 | resolution: {integrity: sha512-yOI6G8WYfr0q8v8rRvE91wbxFU+rJPo760Va4MF6K0I6BZjO4r+xSynkvyPBP9tV1CIEUeRsiidjIs2rzb1CnQ==} 179 | hasBin: true 180 | dependencies: 181 | css: 3.0.0 182 | debug: 4.3.4 183 | glob: 7.2.3 184 | safer-buffer: 2.1.2 185 | sax: 1.2.4 186 | source-map: 0.7.4 187 | transitivePeerDependencies: 188 | - supports-color 189 | dev: true 190 | 191 | /wrappy/1.0.2: 192 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 193 | dev: true 194 | -------------------------------------------------------------------------------- /examples/custom-extension/variables.styl: -------------------------------------------------------------------------------- 1 | color-variables() 2 | --color-1: red; 3 | --color-2: blue; 4 | 5 | :root { 6 | color-variables(); 7 | } 8 | -------------------------------------------------------------------------------- /examples/js-parser/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": [ 3 | "src/global/*.tsx", 4 | "src/global/*.jsx", 5 | ], 6 | "cssvar.extensions": [ 7 | "css", 8 | "tsx", 9 | "ts", 10 | "ejs", 11 | "html" 12 | ] 13 | // "cssvar.postcssSyntax": { 14 | // "postcss-syntax": ["js", "jsx", "ts", "tsx"] 15 | // } 16 | } 17 | -------------------------------------------------------------------------------- /examples/js-parser/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/js-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-parser", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@linaria/core": "^4.1.1", 14 | "@linaria/react": "^4.1.2", 15 | "@stylelint/postcss-css-in-js": "^0.38.0", 16 | "postcss": "^8.4.16", 17 | "postcss-syntax": "^0.36.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/js-parser/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@linaria/core"; 2 | 3 | const rootClass = css` 4 | color: var(--color-green); 5 | background-color: var(--color-blue); 6 | 7 | .head { 8 | font-size: var(--h1); 9 | padding: var(--space-1); 10 | } 11 | `; 12 | 13 | export const App = () => { 14 | let x = 1; 15 | const y = --x; 16 | return ( 17 |
18 |

Hello World {y}

19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/js-parser/src/global/BrandStyles.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@linaria/react"; 2 | 3 | export default styled.div` 4 | :root { 5 | --brand: var(--color-red); 6 | --spacing-0: 0; 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /examples/js-parser/src/global/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@linaria/react"; 2 | 3 | export default styled.div` 4 | :root { 5 | --h1: 44px; 6 | --h2: 32px; 7 | --h3: 24px; 8 | 9 | --color-red: red; 10 | --color-green: lab(50.22% -49 38.39); 11 | --color-blue: blue; 12 | } 13 | `; 14 | 15 | export const RandomComponent = () => { 16 | const classes = ["foo", "bar"]; 17 | 18 | return ( 19 |
20 |

21 | Hell World 22 |

23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /examples/js-parser/src/global/Theme.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@linaria/core"; 2 | 3 | export default css` 4 | :root { 5 | --space-1: 1rem; 6 | --space-2: 2rem; 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /examples/js-parser/src/global/ThemeOverrides.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@linaria/core"; 2 | 3 | export default css` 4 | :root { 5 | --space-1: 2rem; 6 | --space-2: 4rem; 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /examples/js-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/multi-root/README.md: -------------------------------------------------------------------------------- 1 | # Brief 2 | 3 | Multi Root support for CSS variables is pretty new, released with `v1.5`. 4 | Support for CSS variables is pretty similar to single folder support in VSCode. 5 | 6 | Some differences from single folder approach are mentioned below: 7 | 8 | - Some of the extension settings can be either set for the entire project or specific to each root folder 9 | - Switching between CSS files from different Root folders requires file updates for the extension to 10 | trigger again. 11 | - Not sure why this happens, but will try to figure out a fix (if present) in upcoming releases. 12 | -------------------------------------------------------------------------------- /examples/multi-root/multi-root.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { "path": "./proj-1" }, 4 | { "path": "./proj-2" }, 5 | { "path": "./proj-3" }, 6 | { "path": "." }, 7 | ], 8 | "settings": { 9 | "files.exclude": { 10 | "**/.git": true, 11 | "**/.svn": true, 12 | "**/.hg": true, 13 | "**/CVS": true, 14 | "**/.DS_Store": true, 15 | "**/Thumbs.db": true, 16 | "proj-1": true, 17 | "proj-2": true, 18 | "proj-3": true, 19 | }, 20 | // Override default settings and do not lookup for css files recursively 21 | // when in the root folder of the workspace 22 | "cssvar.files": ["*.css"], 23 | "cssvar.enableGotoDef": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/multi-root/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-root", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "open-props": "^1.4.14" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/multi-root/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | open-props: ^1.4.14 5 | 6 | dependencies: 7 | open-props: 1.4.14 8 | 9 | packages: 10 | 11 | /open-props/1.4.14: 12 | resolution: {integrity: sha512-EkTUczMzty8mIvnWGpC41ELIejARiuljsXTEz7YDRKJn5dJ1/njsLTpNhn2rsM3JSW95p4G94ojsgzxbaiFmNA==} 13 | dev: false 14 | -------------------------------------------------------------------------------- /examples/multi-root/proj-1/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.ignore": ["broken.css"], 3 | "cssvar.mode": "error" 4 | } 5 | -------------------------------------------------------------------------------- /examples/multi-root/proj-1/broken.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --blue #333; 3 | } 4 | -------------------------------------------------------------------------------- /examples/multi-root/proj-1/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-1: #4CAF50; 3 | --color-2: #01579B; 4 | } 5 | 6 | .app { 7 | color: var(--color-1); 8 | background-color: var(--color-2); 9 | 10 | /* LOL!! I didn't know adding a var() in the comment makes it visible for diagnostics */ 11 | /* FIXME(phoenisx) When var() function is used with default values, the regex doesn't */ 12 | /* work as expected */ 13 | /* Non declared variable test */ 14 | color: var(--space-2, 2rem); 15 | } 16 | -------------------------------------------------------------------------------- /examples/multi-root/proj-2/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["**/*.scss"], 3 | "cssvar.extensions": ["scss"], 4 | "cssvar.mode": "warn" 5 | } 6 | -------------------------------------------------------------------------------- /examples/multi-root/proj-2/index.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | // Nested rules are not supported in CSS 3 | .child { 4 | color: var(--brand-1); 5 | background-color: var(--brand-3); 6 | border-color: var(--brand-3); 7 | 8 | // Non-declared variable tests 9 | color: var(--space-2); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/multi-root/proj-2/theme.scss: -------------------------------------------------------------------------------- 1 | // Single line comments is not supported in CSS. 2 | $color-1: #FF8A65; 3 | $color-2: #8D6E63; 4 | $color-3: #BDBDBD; 5 | 6 | :root { 7 | --brand-1: #{$color-1}; 8 | --brand-2: #{$color-2}; 9 | --brand-3: #{$color-3}; 10 | } 11 | -------------------------------------------------------------------------------- /examples/multi-root/proj-3/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["../node_modules/open-props/open-props.min.css", "theme*.css"], 3 | "cssvar.ignore": [], 4 | "cssvar.disableSort": true, 5 | "cssvar.themes": [ 6 | "dark", 7 | "dim" 8 | ], 9 | "cssvar.excludeThemedVariables": false, 10 | "cssvar.mode": "warn" 11 | } 12 | -------------------------------------------------------------------------------- /examples/multi-root/proj-3/app.css: -------------------------------------------------------------------------------- 1 | .app { 2 | color: var(--bs-accordion-active-color); 3 | border-radius: var(--bs-border-radius); 4 | border-color: var(--bs-border-color); 5 | } 6 | -------------------------------------------------------------------------------- /examples/multi-root/proj-3/index.css: -------------------------------------------------------------------------------- 1 | @import "./theme.css"; 2 | @import "./theme.dark.css"; 3 | 4 | body { 5 | /* FIXME(phoenisx) Not working. It was working before */ 6 | /* It started working after reload */ 7 | color: var(--primary-text); 8 | background-color: var(--primary-bg); 9 | padding: var(--container-spacing); 10 | 11 | border-radius: var(--radius-2); 12 | border-color: var(--gradient-16); 13 | border-width: var(--border-size-1); 14 | } 15 | -------------------------------------------------------------------------------- /examples/multi-root/proj-3/theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-text: var(--gray-7); 3 | --primary-bg: var(--gray-0); 4 | --container-spacing: var(--size-3); 5 | } 6 | 7 | @media (min-width: 600px) { 8 | :root { 9 | --container-spacing: var(--size-7); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/multi-root/proj-3/theme.dark.css: -------------------------------------------------------------------------------- 1 | :root.dark { 2 | --primary-text: var(--gray-0); 3 | --primary-bg: var(--gray-9); 4 | } 5 | -------------------------------------------------------------------------------- /examples/recursion/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["testing-*.css"], 3 | } 4 | -------------------------------------------------------------------------------- /examples/recursion/01-defined-early.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --foo: #f00; 3 | --bar: var(--foo); 4 | } 5 | 6 | .test { 7 | background: var(--foo); 8 | } 9 | 10 | .test--two { 11 | --foo: var(--bar); 12 | } 13 | -------------------------------------------------------------------------------- /examples/recursion/02-var-fn-not-evaluated.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --foo: #f00; 3 | --joo: 4px; 4 | /* Following does not get normalised in post-processing, because of regex */ 5 | --too: 2px var(--joo); 6 | --bar: var(--too); 7 | } 8 | 9 | .test { 10 | /* --foo should point to #f00, but it isn't righ now */ 11 | /* Need to find a way to set recursive value to previously declared variable */ 12 | background: var(--foo); 13 | } 14 | 15 | .test--two { 16 | --foo: var(--bar); 17 | } 18 | -------------------------------------------------------------------------------- /examples/recursion/03-no-value-defined.css: -------------------------------------------------------------------------------- 1 | /* Folllowing is valid css */ 2 | :root { 3 | --bar: var(--foo); 4 | } 5 | 6 | .test { 7 | background: var(--foo); 8 | } 9 | 10 | .test--two { 11 | --foo: var(--bar); 12 | } 13 | -------------------------------------------------------------------------------- /examples/recursion/04-defined-value-after-use.css: -------------------------------------------------------------------------------- 1 | /* Folllowing is valid css */ 2 | :root { 3 | --bar: var(--foo); 4 | } 5 | 6 | .test { 7 | background: var(--foo); 8 | } 9 | 10 | .test--two { 11 | --foo: var(--bar); 12 | } 13 | 14 | .test--two.define { 15 | --foo: lightgreen; 16 | } 17 | -------------------------------------------------------------------------------- /examples/recursion/testing-base.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --fuzz: #fff; 3 | 4 | --fuzz-large: var(--fuzz); 5 | --boo: var(--not-defined); 6 | } 7 | 8 | @media (min-width: 768px) { 9 | :root { 10 | --boo: 48px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/recursion/testing-index.css: -------------------------------------------------------------------------------- 1 | .card { 2 | background: var(--fuzz); 3 | padding: var(--not-defined); 4 | } 5 | 6 | /* Just updating variables instead of having to re-define each property */ 7 | .card--large { 8 | --fuzz: var(--fuzz-large); 9 | --not-defined: var(--boo); 10 | } 11 | -------------------------------------------------------------------------------- /examples/recursion/testing-solo.css: -------------------------------------------------------------------------------- 1 | /* Folllowing is valid css */ 2 | :root { 3 | --bar: var(--foo); 4 | } 5 | 6 | .test { 7 | background: var(--foo); 8 | } 9 | 10 | .test--two { 11 | --foo: var(--bar); 12 | } 13 | 14 | .test--two.define { 15 | --foo: lightgreen; 16 | } 17 | -------------------------------------------------------------------------------- /examples/recursion/testing-temp.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --pop: violet; 3 | --chop1: var(--pop); 4 | --chop2: var(--chop1); 5 | } 6 | 7 | body { 8 | color: var(--chop2); 9 | } 10 | -------------------------------------------------------------------------------- /examples/safe-parser/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // You will have to import all CSS source files, that contains 3 | // CSS variables for CSS Variable Linting to work. 4 | // For e.g. If you remove `index.css` from below source files list 5 | // CSS Variables defined inside `index.css` file will be considered as unknown 6 | // and thus this extension will lint them as error on usage. 7 | "cssvar.files": ["index.css", "source.*"], 8 | 9 | // Override default setting it to error 10 | // "cssvar.mode": "error" 11 | 12 | // OR 13 | "cssvar.mode": ["warn", { 14 | "ignore": [ 15 | "^--astro-.*", 16 | "ignore-.*?", 17 | "--color-astro-3", 18 | ] 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /examples/safe-parser/index.css: -------------------------------------------------------------------------------- 1 | .app { 2 | color: var(--color-js-1); 3 | background-color: var(--color-tsx-2); 4 | padding: var(--space-1); 5 | border-color: var(--brand-2); 6 | color: var( --color-astro-1 ); 7 | border-width: var(--space-1); 8 | 9 | /* Random Igore linting tests */ 10 | color: var(--color-astro-3); 11 | color: var(--astro-3); 12 | color: var(--astro-ignored); 13 | color: var(--do-not-ignored-this); 14 | color: var(--ignore-me); 15 | color: var(--ignore-me); 16 | color: var(--color-astro-4); 17 | } 18 | 19 | .wrapper { 20 | --color-astro-4: #333; 21 | } 22 | -------------------------------------------------------------------------------- /examples/safe-parser/source.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Layout.astro"; 3 | import Card from "../components/Card.astro"; 4 | --- 5 | 6 | 7 |
8 |

Welcome to Astro

9 |

10 | Check out the src/pages directory to get started.
11 | Code Challenge: Tweak the "Welcome to Astro" message above. 12 |

13 | 35 |
36 |
37 | 38 | 49 | -------------------------------------------------------------------------------- /examples/safe-parser/source.jsx: -------------------------------------------------------------------------------- 1 | import { styled, css } from "styled-components"; 2 | 3 | export const Button = styled.a` 4 | :root { 5 | --color-js-1: #f30; 6 | --color-js-2: #f0aa44; 7 | } 8 | /* This renders the buttons above... Edit me! */ 9 | display: inline-block; 10 | border-radius: 3px; 11 | padding: 0.5rem 0; 12 | margin: 0.5rem 1rem; 13 | width: 11rem; 14 | background: transparent; 15 | color: white; 16 | border: 2px solid white; 17 | 18 | /* The GitHub button is a primary button 19 | * edit this to target it specifically! */ 20 | ${props => 21 | props.primary && 22 | css` 23 | background: white; 24 | color: black; 25 | `} 26 | `; 27 | -------------------------------------------------------------------------------- /examples/safe-parser/source.scss: -------------------------------------------------------------------------------- 1 | // Single line comments is not supported in CSS. 2 | $color-1: #FF8A65; 3 | $color-2: #8D6E63; 4 | $color-3: #BDBDBD; 5 | 6 | :root { 7 | --space-1: 1rem; 8 | --space-2: 2rem; 9 | // This is a little broken, since safe-parser considers 10 | // #{$color-1} as a separate rule. LOL!! Adding a single 11 | // line comments completely loses the following variable 12 | 13 | --brand-1: #{$color-1}; 14 | --brand-2: #{$color-2}; 15 | --brand-3: #{$color-3}; 16 | } 17 | 18 | .app { 19 | // Nested rules are not supported in CSS 20 | .child { 21 | color: var(--brand-1); 22 | background-color: var(--brand-3); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/safe-parser/source.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { styled } from "styled-components"; 3 | 4 | // For now variables inside some rules are only getting parsed 5 | export const Title = styled.h1` 6 | --color-tsx-1: #34aa12; 7 | .child { 8 | --color-tsx-2: #aa2399; 9 | } 10 | /* Text centering won't break if props.upsidedown is falsy */ 11 | ${props => props.upsidedown && "transform: rotate(180deg);"} 12 | ${props => props.upsidedown && "--wont-work: #334;"} 13 | text-align: center; 14 | `; 15 | -------------------------------------------------------------------------------- /examples/scss/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["index.scss"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/scss/_01-variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --gray-0: #f8f9fa; 3 | --gray-1: #f1f3f5; 4 | --gray-2: #e9ecef; 5 | --gray-3: #dee2e6; 6 | --gray-4: #ced4da; 7 | --gray-5: #adb5bd; 8 | --gray-6: #868e96; 9 | --gray-7: #495057; 10 | --gray-8: #343a40; 11 | --gray-9: #212529; 12 | } 13 | -------------------------------------------------------------------------------- /examples/scss/_02-variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --red-0: #fff5f5; 3 | --red-1: #ffe3e3; 4 | --red-2: #ffc9c9; 5 | --red-3: #ffa8a8; 6 | --red-4: #ff8787; 7 | --red-5: #ff6b6b; 8 | --red-6: #fa5252; 9 | --red-7: #f03e3e; 10 | --red-8: #e03131; 11 | --red-9: #c92a2a; 12 | } 13 | -------------------------------------------------------------------------------- /examples/scss/_base.scss: -------------------------------------------------------------------------------- 1 | $font-stack: Helvetica, sans-serif; 2 | $primary-color: #333; 3 | 4 | @mixin variables { 5 | --size-sm: 12px; 6 | --size-md: 18px; 7 | --size-lg: 2rem; 8 | 9 | --color-red-50: #ffebee; 10 | --color-red-100: rgb(255, 205, 210); 11 | --color-purple-300: hsl(291.25, 46.6%, 59.61%); 12 | --color-purple-500-alpha: hsla(277.32, 70.17%, 35.49%, 0.67); 13 | } 14 | 15 | :root { 16 | @include variables; 17 | } 18 | 19 | body { 20 | font: 100% $font-stack; 21 | color: $primary-color; 22 | } 23 | -------------------------------------------------------------------------------- /examples/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | @forward '01-variables'; 2 | @forward '_02-variables.scss'; 3 | -------------------------------------------------------------------------------- /examples/scss/index.scss: -------------------------------------------------------------------------------- 1 | @use 'variables'; 2 | @use 'base'; 3 | 4 | .inverse { 5 | background-color: base.$primary-color; 6 | color: var(--red-8); 7 | border-color: var(--color-purple-300); 8 | border-width: var(--size-sm); 9 | } 10 | -------------------------------------------------------------------------------- /examples/theming/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.themes": ["dark", "dim"], 3 | "cssvar.excludeThemedVariables": true, 4 | } 5 | -------------------------------------------------------------------------------- /examples/theming/styles.css: -------------------------------------------------------------------------------- 1 | .card { 2 | color: var(--card-color); 3 | border-radius: 4px; 4 | background-color: var(--card-background); 5 | } 6 | -------------------------------------------------------------------------------- /examples/theming/theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-red-100: #FFCDD2; 3 | --color-red-500: #F44336; 4 | --color-red-900: #B71C1C; 5 | 6 | --color-green-100: #C8E6C9; 7 | --color-green-500: #4CAF50; 8 | --color-green-900: #1B5E20; 9 | 10 | } 11 | 12 | body { 13 | --card-color: var(--color-red-900); 14 | --card-background: var(--color-red-100); 15 | } 16 | 17 | body.dark { 18 | --card-color: var(--color-green-900); 19 | --card-background: var(--color-green-100); 20 | } 21 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.postcssPlugins": [ 3 | // `cwd` option is required only if tokencss config is not declared directly under project root 4 | ["@tokencss/postcss", { "cwd": "config" }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/base.css: -------------------------------------------------------------------------------- 1 | @inject "tokencss:base"; 2 | 3 | :root { 4 | --color-red: red; 5 | } 6 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/config/token.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://tokencss.com/schema@0.0.1", 3 | "extends": ["@tokencss/core/preset"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--color-red-2); 3 | background-color: var(--color-blue-4); 4 | padding: var(--space-2xl); 5 | } 6 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tokencss-plugin", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@tokencss/core": "^0.1.0", 14 | "@tokencss/postcss": "^0.0.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/tokencss-plugin/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | '@tokencss/core': ^0.1.0 5 | '@tokencss/postcss': ^0.0.5 6 | 7 | dependencies: 8 | '@tokencss/core': 0.1.0 9 | '@tokencss/postcss': 0.0.5 10 | 11 | packages: 12 | 13 | /@proload/core/0.2.2: 14 | resolution: {integrity: sha512-HYQEblYXIpW77kvGyW4penEl9D9e9MouPhTqVaDz9+QVFliYjsq18inTfnfTa81s3oraPVtTk60tqCWOf2fKGQ==} 15 | dependencies: 16 | deepmerge: 4.2.2 17 | escalade: 3.1.1 18 | dev: false 19 | 20 | /@proload/plugin-json/0.2.0_@proload+core@0.2.2: 21 | resolution: {integrity: sha512-uZRA24JC7PMyT092D1EgcHxoGexAwmPltet/WPKVUL+bQ03Y5AceDL672XaUPCaquL1CbAnurky+cp48yFQ+aA==} 22 | peerDependencies: 23 | '@proload/core': ^0.2.2 24 | dependencies: 25 | '@proload/core': 0.2.2 26 | jsonc-register: 0.2.0 27 | dev: false 28 | 29 | /@tokencss/core/0.1.0: 30 | resolution: {integrity: sha512-vSWoiZ1chaC4qqhAxbrRoEnPEGeRby1SQYf8ayqtui5+yizwp56A+WRev8xgugIuucH4Jbg4DkNvKcwc1XvzPg==} 31 | dependencies: 32 | dlv: 1.1.3 33 | dev: false 34 | 35 | /@tokencss/postcss/0.0.5: 36 | resolution: {integrity: sha512-iv4php+M7s/Imrbla9WO0TcoedHaej1bl+74Un8pxttD7oGLk+CblqsbMs1DotES4C9BQEUiFG6tm0ezJ4c4hw==} 37 | peerDependencies: 38 | postcss: ^8.4.5 39 | dependencies: 40 | '@proload/core': 0.2.2 41 | '@proload/plugin-json': 0.2.0_@proload+core@0.2.2 42 | '@tokencss/core': 0.1.0 43 | magic-string: 0.25.9 44 | postcss-value-parser: 4.2.0 45 | dev: false 46 | 47 | /deepmerge/4.2.2: 48 | resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} 49 | engines: {node: '>=0.10.0'} 50 | dev: false 51 | 52 | /dlv/1.1.3: 53 | resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 54 | dev: false 55 | 56 | /escalade/3.1.1: 57 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 58 | engines: {node: '>=6'} 59 | dev: false 60 | 61 | /jsonc-register/0.2.0: 62 | resolution: {integrity: sha512-wzsgJUdIntyH0SsoMFf1K4SAEipHAtKTdY1OZakDY/azflRiSrK1CsVHgc0hHRhlJ3MtB91f6O9tkluQ1XUFsA==} 63 | dependencies: 64 | pirates: 4.0.5 65 | strip-json-comments: 3.1.1 66 | dev: false 67 | 68 | /magic-string/0.25.9: 69 | resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} 70 | dependencies: 71 | sourcemap-codec: 1.4.8 72 | dev: false 73 | 74 | /pirates/4.0.5: 75 | resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} 76 | engines: {node: '>= 6'} 77 | dev: false 78 | 79 | /postcss-value-parser/4.2.0: 80 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 81 | dev: false 82 | 83 | /sourcemap-codec/1.4.8: 84 | resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} 85 | dev: false 86 | 87 | /strip-json-comments/3.1.1: 88 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 89 | engines: {node: '>=8'} 90 | dev: false 91 | -------------------------------------------------------------------------------- /examples/with-plugins/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": [ 3 | "src/tailwind.css", 4 | "src/other.css", 5 | "src/tokencss.css", 6 | ], 7 | "cssvar.postcssPlugins": [ 8 | ["postcss-import"], 9 | ["postcss-nested", { 10 | "unwrap": ["phone"] 11 | }], 12 | 13 | // Use tailwindcss plugin with precaution, as it 14 | // processes every css passed to this extension. 15 | // Always use `cssvar.files` to properly define your source files 16 | // when using tailwindcss, to keep this extension parsing fast enough. 17 | "tailwindcss", 18 | 19 | "@tokencss/postcss" 20 | // Do not use `postcss-preset-env` plugin, as it makes the extension 21 | // parsing pretty slow, which happens on each source file change. 22 | // "postcss-preset-env" 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /examples/with-plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-plugins", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "postcss-import": "^15.0.0", 14 | "postcss-nested": "^5.0.6", 15 | "tailwindcss": "^3.1.8" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/with-plugins/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--base); 3 | background-color: rgba(var(--color-teal-6-rgb) , 0.3); 4 | color: var(--color-grape-5); 5 | padding: var(--tw-border-spacing-x) var(--tw-border-spacing-y); 6 | scale: var(--tw-scale-x); 7 | } 8 | -------------------------------------------------------------------------------- /examples/with-plugins/src/other.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --base: #333; 3 | --color-1: red; 4 | --color-2: green; 5 | --color-3: blue; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/with-plugins/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/with-plugins/src/testing.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willofindie/vscode-cssvar/dd48ea378f580aa840d93f58d257f2abcf54f535/examples/with-plugins/src/testing.js -------------------------------------------------------------------------------- /examples/with-plugins/src/tokencss.css: -------------------------------------------------------------------------------- 1 | @inject "tokencss:base"; 2 | -------------------------------------------------------------------------------- /examples/with-plugins/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.css"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /examples/with-plugins/token.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://tokencss.com/schema@0.0.1", 3 | "extends": ["@tokencss/core/preset"] 4 | } 5 | -------------------------------------------------------------------------------- /feature-contributions.md: -------------------------------------------------------------------------------- 1 |
2 | CssVar Icon 6 |
7 | 8 | 9 |

10 | CSS Variables 11 |

12 | 13 |
14 |

15 | Please vote/rate and star this project to show your support. 16 | ❤️ 17 |

18 | 19 | 20 | 21 | 22 | 24 |    25 | 26 | 27 | 28 |    29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 |    37 | 38 | 39 | 40 |
41 | 42 | ## 🛠 Supported Configs: 43 | 44 | This Extension supports the following properties as of now: 45 | 46 |
47 | Settings 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 70 | 71 | 72 | 73 | 76 | 77 | 81 | 82 | 83 | 84 | 87 | 88 | 92 | 93 | 94 | 98 | 99 | 100 | 118 | 119 | 120 | 124 | 128 | 129 | 134 | 135 | 136 | 140 | 141 | 142 | 147 | 148 | 149 | 153 | 154 | 155 | 160 | 161 | 162 | 163 | 164 | 165 | 170 | 171 | 172 | 173 | 174 | 175 | 180 | 181 | 182 | 183 | 184 | 185 | 190 | 191 | 192 | 196 | 202 | 203 | 208 | 209 | 210 | 214 | 223 | 224 | 229 | 234 | 235 | 236 | 240 | 248 | 253 | 257 | 258 | 259 |
SettingDescriptionTypeDefault
cssvar.enable 63 | Enable/Disable extension for a workspace/folder 64 | boolean
67 |
true
68 |
69 |
cssvar.files 74 | Relative/Absolute paths to CSS variable source files 75 | string[]
78 |
["**/*.css"]
79 |
80 |
cssvar.ignore 85 | Relative/Absolute paths to file/folder sources to be ignored 86 | string[]
89 |
["**/node_modules/**"]
90 |
91 |
95 | cssvar.extensions 96 |
Use language identifiers mentioned in this doc 97 |
File extensions in which IntelliSense will be enabledstring[] 101 |
102 |
[
103 |   "css",
104 |   "scss",
105 |   "sass",
106 |   "less",
107 |   "postcss",
108 |   "vue",
109 |   "svelte",
110 |   "astro",
111 |   "ts",
112 |   "tsx",
113 |   "js",
114 |   "jsx"
115 | ]
116 |
117 |
121 | cssvar.themes 122 |
Helps to bucket CSS variables based on themes used in any project 123 |
125 |
CSS Theme class names used in source files 126 |
E.g.
["dark","dim"]
127 |
string[] 130 |
131 |
[]
132 |
133 |
137 | cssvar.excludeThemedVariables 138 |
If true, hides duplicate theme variables from the list 139 |
Exclude themed variables to remove unnecessary duplicatesboolean 143 |
144 |
false
145 |
146 |
150 | cssvar.disableSort 151 |
Intellisense list won't be sorted 152 |
Disables default sorting applied by VSCodeboolean 156 |
157 |
false
158 |
159 |
cssvar.enableColorsEnable VSCode's Color Representation feature when trueboolean 166 |
167 |
true
168 |
169 |
cssvar.enableGotoDefEnable VSCode's Goto Definition feature for CSS Variableboolean 176 |
177 |
true
178 |
179 |
cssvar.enableHoverEnable VScode's Hover IntelliSense feature for CSS Variablesboolean 186 |
187 |
true
188 |
189 |
193 | cssvar.postcssSyntax 194 |
Details for this can be read here: Customize Extension 195 |
197 |
Provides custom syntax parser for the mapped file extensions. 198 |
E.g.
{
199 |   "sugarss": ["sss"]
200 | }
201 |
Record<string,string[]
>
204 |
205 |
{}
206 |
207 |
211 | cssvar.postcssPlugins 212 |
Provide PostCSS Plugins to support custom CSS features 213 |
215 |
E.g. 216 |
["postcss-nested"]
217 | Or 218 |
[[
219 |   "postcss-nested",
220 |   {"unwrap": ["phone"]}
221 | ]]
222 |
string[]
225 | | [
226 |     string,
227 |     object
228 |   ][]
230 |
231 |
[]
232 |
233 |
237 | cssvar.mode 238 |
Enable/Disable CSS variable linting modes. 239 |
241 |
E.g.
["error", {
242 |   ignore: [
243 |     "--dynamic",
244 |     "dy[nN].*?c$"
245 |   ]
246 | }]
247 |
string
249 | | [
250 |     string,
251 |     {ignore: string[]}
252 |   ]

254 |
"off"
255 |
256 |
260 |

261 | 262 |
263 | 264 |
265 | Supported Languages/Extensions 266 | 267 | ### CSS 268 | Any file with extensions `.css` and `.postcss` will be treated as CSS file. 269 | 270 | ### SASS 271 | Any file with extensiosn `.scss` and `sass` will be treated as SCSS and SASS files respectively. 272 | 273 | ### LESS 274 | Any file with extension `.less` will be treated as LESS file. 275 | 276 | ### SVELTE 277 | Any file with extension `.svelte` will be treated as Svelte file. 278 | 279 | ### VUE 280 | Any file with extension `.vue` will be treated as Vue file. 281 | 282 | ### ASTRO 283 | Any file with extension `.astro` will be treated as Astro file. 284 | 285 | ### JS 286 | Any file with extension `.js` or `.jsx` will be treated as Javascript files. 287 | 288 | ### TS 289 | Any file with extension `.ts` or `.tsx` will be treated as Typescript files. 290 | 291 | --- 292 | 293 | To support more extension/languages where this extension can trigger 294 | it's IntelliSense, [please raise a request](https://github.com/willofindie/vscode-cssvar/issues/new) 295 | 296 | To enable this extension for less languages, use `cssvar.extensions` settings to override the defaults. 297 | -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willofindie/vscode-cssvar/dd48ea378f580aa840d93f58d257f2abcf54f535/img/icon.png -------------------------------------------------------------------------------- /img/sponsors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willofindie/vscode-cssvar/dd48ea378f580aa840d93f58d257f2abcf54f535/img/sponsors.png -------------------------------------------------------------------------------- /jest-global-setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | module.exports = async function (_globalConfig, _projectConfig) { 3 | // console.log(globalConfig.testPathPattern); 4 | // console.log(projectConfig.cache); 5 | }; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/en/configuration.html 4 | */ 5 | 6 | module.exports = { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/pd/bntz0rrx17l9c3cfjwj8cn3csnfqbw/T/jest_e8u0e4", 15 | 16 | // Automatically clear mock calls and instances between every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | // collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | // coverageProvider: "babel", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | globalSetup: "/jest-global-setup.js", 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "json", 77 | // "jsx", 78 | // "ts", 79 | // "tsx", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | moduleNameMapper: { 85 | "^culori/fn$": "/node_modules/culori/src/index-fn.js", 86 | }, 87 | 88 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 89 | // modulePathIgnorePatterns: [], 90 | 91 | // Activates notifications for test results 92 | // notify: false, 93 | 94 | // An enum that specifies notification mode. Requires { notify: true } 95 | // notifyMode: "failure-change", 96 | 97 | // A preset that is used as a base for Jest's configuration 98 | // preset: undefined, 99 | 100 | // Run tests from one or more projects 101 | // projects: undefined, 102 | 103 | // Use this configuration option to add custom reporters to Jest 104 | // reporters: undefined, 105 | 106 | // Automatically reset mock state between every test 107 | // resetMocks: false, 108 | 109 | // Reset the module registry before running each individual test 110 | // resetModules: false, 111 | 112 | // A path to a custom resolver 113 | // resolver: undefined, 114 | 115 | // Automatically restore mock state between every test 116 | // restoreMocks: false, 117 | 118 | // The root directory that Jest should scan for tests and modules within 119 | // rootDir: undefined, 120 | 121 | // A list of paths to directories that Jest should use to search for files in 122 | // roots: [ 123 | // "" 124 | // ], 125 | 126 | // Allows you to use a custom runner instead of Jest's default test runner 127 | // runner: "jest-runner", 128 | 129 | // The paths to modules that run some code to configure or set up the testing environment before each test 130 | // setupFiles: [], 131 | 132 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 133 | // setupFilesAfterEnv: [], 134 | 135 | // The number of seconds after which a test is considered as slow and reported as such in the results. 136 | // slowTestThreshold: 5, 137 | 138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 139 | // snapshotSerializers: [], 140 | 141 | // The test environment that will be used for testing 142 | testEnvironment: "node", 143 | 144 | // Options that will be passed to the testEnvironment 145 | // testEnvironmentOptions: {}, 146 | 147 | // Adds a location field to test results 148 | // testLocationInResults: false, 149 | 150 | // The glob patterns Jest uses to detect test files 151 | // testMatch: [ 152 | // "**/__tests__/**/*.[jt]s?(x)", 153 | // "**/?(*.)+(spec|test).[tj]s?(x)" 154 | // ], 155 | 156 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 157 | // testPathIgnorePatterns: [ 158 | // "/node_modules/" 159 | // ], 160 | 161 | // The regexp pattern or array of patterns that Jest uses to detect test files 162 | // testRegex: [], 163 | 164 | // This option allows the use of a custom results processor 165 | // testResultsProcessor: undefined, 166 | 167 | // This option allows use of a custom test runner 168 | // testRunner: "jasmine2", 169 | 170 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 171 | // testURL: "http://localhost", 172 | 173 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 174 | // timers: "real", 175 | 176 | // A map from regular expressions to paths to transformers 177 | // transform: undefined, 178 | 179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 180 | transformIgnorePatterns: ["/node_modules/?!culori"], 181 | 182 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 183 | // unmockedModulePathPatterns: undefined, 184 | 185 | // Indicates whether each individual test should be reported during the run 186 | // verbose: undefined, 187 | 188 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 189 | // watchPathIgnorePatterns: [], 190 | 191 | // Whether to use watchman for file crawling 192 | // watchman: true, 193 | }; 194 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: false, 3 | trailingComma: "es5", 4 | arrowParens: "avoid", 5 | proseWrap: "preserve", 6 | }; 7 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import replace from "@rollup/plugin-replace"; 4 | import json from "@rollup/plugin-json"; 5 | import esbuild from "rollup-plugin-esbuild"; 6 | import pkg from "./package.json" with { type: "json" }; 7 | import tsConfig from "./tsconfig.json" with { type: "json" }; 8 | 9 | const isProd = process.env.NODE_ENV === "production"; 10 | 11 | /** 12 | * @type {import("rollup").RollupOptions[]} 13 | */ 14 | export default [ 15 | { 16 | input: "src/extension.ts", 17 | external: ["vscode"], 18 | output: { 19 | // file: pkg.main, 20 | dir: "out", 21 | format: "cjs", 22 | }, 23 | plugins: [ 24 | replace({ 25 | "process.env.NODE_ENV": JSON.stringify( 26 | process.env.NODE_ENV || "development" 27 | ), 28 | preventAssignment: true, 29 | }), 30 | json(), 31 | resolve({ 32 | browser: false, 33 | preferBuiltins: true, 34 | }), 35 | commonjs({ 36 | ignoreGlobal: true, 37 | }), 38 | esbuild({ 39 | minify: isProd, 40 | sourceMap: !isProd, 41 | target: tsConfig.compilerOptions.target, 42 | define: { 43 | __VERSION__: JSON.stringify(pkg.version), 44 | }, 45 | }), 46 | ], 47 | }, 48 | ]; 49 | -------------------------------------------------------------------------------- /src/color-parser.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BasicParserReturn, 3 | parseHsl, 4 | parseHex, 5 | parseHwb, 6 | parseLab, 7 | parseLch, 8 | parseNamed, 9 | parseRgb, 10 | parseTransparent, 11 | serializeRgb, 12 | convertHwbToRgb, 13 | convertHslToRgb, 14 | convertLchToLab, 15 | convertLabToRgb, 16 | LCH, 17 | RGB, 18 | } from "culori/fn"; 19 | 20 | const CONVERTERS = { 21 | hsl: convertHslToRgb, 22 | hwb: convertHwbToRgb, 23 | lch: (color: LCH) => convertLabToRgb(convertLchToLab(color)), 24 | lab: convertLabToRgb, 25 | rgb: (color: RGB) => color, 26 | }; 27 | const parsers = [ 28 | parseHsl, 29 | parseHex, 30 | parseHwb, 31 | parseLab, 32 | parseLch, 33 | parseNamed, 34 | parseRgb, 35 | parseTransparent, 36 | ]; 37 | 38 | export const parseColor = (color: string) => { 39 | let parsedColor: BasicParserReturn | undefined; 40 | for (const parser of parsers) { 41 | const value = parser(color); 42 | if (value) { 43 | parsedColor = value; 44 | } 45 | } 46 | 47 | return parsedColor; 48 | }; 49 | 50 | export const parseToRgb = (color: string) => { 51 | const parsedColor = parseColor(color); 52 | 53 | if (parsedColor) { 54 | return CONVERTERS[parsedColor.mode](parsedColor as any); 55 | } 56 | 57 | return null; 58 | }; 59 | 60 | export const serializeColor = ( 61 | color: string 62 | ): { 63 | isColor: boolean; 64 | color: string; 65 | } => { 66 | /** 67 | * There's a bug in culori parser, where even a simple number string that 68 | * contains hex digits is considered as a hex value. 69 | * For e.g.: culori.formatRgb("106") is not undefined 70 | * 71 | * This regex makes sure if color starts and ends with digits, it means it's 72 | * a number and not a color. 73 | */ 74 | if (/^\d+$/.test(color)) { 75 | return { 76 | isColor: false, 77 | color, 78 | }; 79 | } 80 | 81 | const parsedColor = parseColor(color); 82 | 83 | if (parsedColor) { 84 | return { 85 | isColor: true, 86 | color: serializeRgb(CONVERTERS[parsedColor.mode](parsedColor as any)), 87 | }; 88 | } 89 | 90 | return { 91 | isColor: false, 92 | color, 93 | }; 94 | }; 95 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | export class NoWorkspaceError extends Error { 2 | name = "NoWorkspaceError"; 3 | } 4 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { 2 | languages, 3 | window, 4 | ExtensionContext, 5 | workspace, 6 | FileSystemWatcher, 7 | RelativePattern, 8 | TextEditor, 9 | } from "vscode"; 10 | import { CssColorProvider } from "./providers/color-provider"; 11 | import { CssCompletionProvider } from "./providers/completion-provider"; 12 | import { CACHE, EXTENSION_NAME } from "./constants"; 13 | import { CssDefinitionProvider } from "./providers/definition-provider"; 14 | import { LOGGER } from "./logger"; 15 | import { setup } from "./main"; 16 | import { parseFiles } from "./parser"; 17 | import { isObjectProperty } from "./utils"; 18 | import { CssHoverProvider } from "./providers/hover-provider"; 19 | import { subscribeToDocumentChanges } from "./providers/diagnostics"; 20 | 21 | const watchers: FileSystemWatcher[] = []; 22 | 23 | /** 24 | * Main Function from where the Plugin loads 25 | * @param context 26 | */ 27 | export async function activate(context: ExtensionContext): Promise { 28 | try { 29 | const { config } = await setup(); 30 | if (!config[CACHE.activeRootPath].enable) { 31 | return; 32 | } 33 | 34 | const [, errorPaths] = await parseFiles(config, { parseAll: true }); // Cache Parsed CSS Vars for all Root folders 35 | if (errorPaths.length > 0) { 36 | const relativePaths = errorPaths; 37 | LOGGER.error( 38 | "Failed to parse CSS variables in files:", 39 | `\n\n${relativePaths.join("\n\n")}` 40 | ); 41 | } 42 | 43 | // Sequence of subscriptions matter. 44 | context.subscriptions.push( 45 | window.onDidChangeActiveTextEditor(updateStatus) 46 | ); 47 | 48 | const completionDisposable = languages.registerCompletionItemProvider( 49 | config[CACHE.activeRootPath].extensions, 50 | new CssCompletionProvider(), 51 | "-", 52 | "v", 53 | "a", 54 | "r", 55 | "(" 56 | ); 57 | context.subscriptions.push(completionDisposable); 58 | 59 | if (config[CACHE.activeRootPath].enableColors) { 60 | const colorDisposable = languages.registerColorProvider( 61 | config[CACHE.activeRootPath].extensions, 62 | new CssColorProvider() 63 | ); 64 | context.subscriptions.push(colorDisposable); 65 | } 66 | 67 | if (config[CACHE.activeRootPath].enableGotoDef) { 68 | const definitionDisposable = languages.registerDefinitionProvider( 69 | config[CACHE.activeRootPath].extensions, 70 | new CssDefinitionProvider() 71 | ); 72 | context.subscriptions.push(definitionDisposable); 73 | } 74 | 75 | if (config[CACHE.activeRootPath].enableHover) { 76 | const definitionDisposable = languages.registerHoverProvider( 77 | config[CACHE.activeRootPath].extensions, 78 | new CssHoverProvider() 79 | ); 80 | context.subscriptions.push(definitionDisposable); 81 | } 82 | 83 | const cssvarDiagnostics = 84 | languages.createDiagnosticCollection(EXTENSION_NAME); 85 | context.subscriptions.push(cssvarDiagnostics); 86 | subscribeToDocumentChanges(context, cssvarDiagnostics); 87 | 88 | //#region File Watcher 89 | /** 90 | * Following code will help in watching file changes, without any 3rd-party libs 91 | * This helps to re-calculate CSS Variables, if there is any change. 92 | */ 93 | const watchers: FileSystemWatcher[] = []; 94 | const folders = workspace.workspaceFolders || []; 95 | for (const folder of folders) { 96 | for (const path of CACHE.filesToWatch[folder.uri.fsPath]) { 97 | const relativePattern = new RelativePattern(path, "*"); 98 | const watcher = workspace.createFileSystemWatcher(relativePattern); 99 | watcher.onDidChange(async () => { 100 | const [, errorPaths] = await parseFiles(CACHE.config); // Cache Parsed CSS Vars 101 | if (errorPaths.length > 0) { 102 | const relativePaths = errorPaths; 103 | window.showWarningMessage( 104 | "Failed to parse CSS variables in files:", 105 | `\n\n${relativePaths.join("\n\n")}` 106 | ); 107 | } 108 | }); 109 | 110 | watcher.onDidDelete(uri => { 111 | // Though I don't think this is of any benefit. 112 | CACHE.filesToWatch[folder.uri.fsPath].delete(uri.fsPath); 113 | }); 114 | 115 | watchers.push(watcher); 116 | } 117 | } 118 | //#endregion 119 | } catch (err) { 120 | if (err instanceof Error) { 121 | window.showErrorMessage(err.message); 122 | LOGGER.warn("Failed to Activate extension: ", err); 123 | } 124 | } 125 | } 126 | 127 | // this method is called when your extension is deactivated 128 | export function deactivate(): void { 129 | watchers.forEach(watcher => { 130 | watcher.dispose(); 131 | }); 132 | } 133 | 134 | /** 135 | * References: 136 | * - https://github.com/microsoft/vscode-extension-samples/blob/main/basic-multi-root-sample/src/extension.ts 137 | * - https://github.com/Microsoft/vscode-extension-samples/blob/main/configuration-sample/src/extension.ts 138 | */ 139 | function updateStatus(event: TextEditor | undefined) { 140 | if (event) { 141 | if (isObjectProperty(event, "document")) { 142 | const rootPath = 143 | workspace.getWorkspaceFolder((event).document.uri)?.uri 144 | .fsPath || CACHE.activeRootPath; 145 | CACHE.activeRootPath = rootPath; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { EXTENSION_NAME } from "./constants"; 3 | 4 | export const LOGGER = { 5 | log: (...args: any[]) => { 6 | if (process.env.NODE_ENV === "development") { 7 | console.log(`[${EXTENSION_NAME}]: `, ...args); 8 | } 9 | }, 10 | info: (...args: any[]) => { 11 | if (process.env.NODE_ENV !== "test") { 12 | console.info(`[${EXTENSION_NAME}]: `, ...args); 13 | } 14 | }, 15 | warn: (...args: any[]) => { 16 | if (process.env.NODE_ENV !== "test") { 17 | console.warn(`[${EXTENSION_NAME}]: `, ...args); 18 | } 19 | }, 20 | error: (...args: any[]) => { 21 | if (process.env.NODE_ENV !== "test") { 22 | console.error(`[${EXTENSION_NAME}]: `, ...args); 23 | } 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/pre-processors/index.ts: -------------------------------------------------------------------------------- 1 | import { CssExtensions, JsExtensions } from "../constants"; 2 | import { templateLiteralPreProcessor, JS_BLOCK } from "./template-literals"; 3 | 4 | export default async function preProcessContent( 5 | input: string, 6 | ext: CssExtensions | JsExtensions 7 | ) { 8 | switch (ext) { 9 | case "js": 10 | case "jsx": 11 | case "ts": 12 | case "tsx": 13 | return await templateLiteralPreProcessor(input); 14 | 15 | default: 16 | return input; 17 | } 18 | } 19 | 20 | export { JS_BLOCK }; 21 | -------------------------------------------------------------------------------- /src/pre-processors/template-literals.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This processor is responsible to extract template literals and provide a string representation 3 | * of the css-in-js into compatible scss string, with their positions intact so that goto 4 | * definitions work with JS sources as well. 5 | * 6 | * E.g A JS file with styled-component shown below: 7 | * 8 | * ```js 9 | * 01 | // RootComponent.jsx 10 | * 02 | const Title = styled.div` 11 | * 03 | --color-1: red; 12 | * 04 | --color-2: blue; 13 | * 05 | font-size: 1.5em; 14 | * 06 | text-align: center; 15 | * 07 | color: var(--color-1) 16 | * 08 | `; 17 | * 09 | 18 | * 10 | render( 19 | * 11 | 20 | * 12 | 21 | * 13 | Hello World! 22 | * 14 | 23 | * 15 | 24 | * 16 | ); 25 | * ``` 26 | * 27 | * should become something as follows: 28 | * ```scss 29 | * 01 | 30 | * 02 | :root { 31 | * 03 | --color-1: red; 32 | * 04 | --color-2: blue; 33 | * 05 | font-size: 1.5em; 34 | * 06 | text-align: center; 35 | * 07 | color: var(--color-1) 36 | * 08 | } 37 | * 09 | 38 | * 10 | 39 | * 11 | 40 | * 12 | 41 | * 13 | 42 | * 14 | 43 | * 15 | 44 | * 16 | 45 | * ``` 46 | * 47 | * Things to consider: 48 | * - If JS file is considered as source, it shouldn't throw error while parsing and affect user ongoing task 49 | * - JS files can continously change, which can trigger the parser many times. This is fine 50 | * as parser triggers only when file is saved. Though, this a users responsibility to keep 51 | * such CSS variable source files in different module, that doesn't change so frequently. 52 | */ 53 | 54 | const TEMPLATE_START_END = "`".charCodeAt(0); 55 | const TEMPLATE_HEAD_START = "$".charCodeAt(0); 56 | const JS_BLOCK_START = "{".charCodeAt(0); 57 | const JS_BLOCK__END = "}".charCodeAt(0); 58 | const TEMPLATE_ESCAPE = "\\".charCodeAt(0); 59 | 60 | // Carriage Returns can be ignored for line breaks, because 61 | // for every line break, irrespective of platform, it always ends 62 | // with a line feed. 63 | const LF = "\n".charCodeAt(0); 64 | 65 | export const JS_BLOCK = "jsblock"; 66 | export const templateLiteralPreProcessor = async (input: string) => { 67 | let charIndex = 0; 68 | let started = false; 69 | let jsCodeStarted = false; 70 | let jsBlockDepth = 0; 71 | const stringBuilder: string[] = []; 72 | 73 | while (charIndex < input.length) { 74 | const code = input.charCodeAt(charIndex); 75 | const nextCode = input.charCodeAt(charIndex + 1); 76 | const char = input.charAt(charIndex); 77 | const nextChar = input.charAt(charIndex + 1); 78 | 79 | if (started) { 80 | // This is inside template literal 81 | if (jsCodeStarted && jsBlockDepth === 0 && code === JS_BLOCK__END) { 82 | // Template literals with JS expressions are ignored, thus we need to replace it with some CSS content 83 | stringBuilder.push(JS_BLOCK); 84 | jsCodeStarted = false; 85 | } else if (jsCodeStarted) { 86 | // Ignore every char here 87 | if (code === JS_BLOCK_START) { 88 | jsBlockDepth += 1; 89 | } else if (code === JS_BLOCK__END) { 90 | jsBlockDepth -= 1; 91 | } else if (code === LF) { 92 | stringBuilder.push("/* */\n"); 93 | } else if (code === TEMPLATE_START_END) { 94 | // Unexpected end, this is when improper JS file is saved without end tokens 95 | stringBuilder.push("}"); 96 | started = false; 97 | jsCodeStarted = false; 98 | } 99 | charIndex += 1; 100 | continue; 101 | } else if (code === TEMPLATE_ESCAPE) { 102 | stringBuilder.push(nextChar); 103 | charIndex += 1; 104 | } else if (code === TEMPLATE_HEAD_START && nextCode === JS_BLOCK_START) { 105 | // Start of JS code block, which we can ignore and replace with empty spaces/line breaks for proper char positions 106 | jsCodeStarted = true; 107 | charIndex += 1; 108 | } else if (code === TEMPLATE_START_END) { 109 | stringBuilder.push("}"); 110 | started = false; 111 | } else { 112 | stringBuilder.push(char); 113 | } 114 | } else if (code === TEMPLATE_START_END && !started) { 115 | // This is start of the template literal 116 | stringBuilder.push(":root {"); 117 | started = true; 118 | } else if (code === LF) { 119 | stringBuilder.push("\n"); 120 | } 121 | // console.log(`CharCode: ${code}, Char: ${char}, index: ${charIndex}`) 122 | charIndex += 1; 123 | } 124 | 125 | return stringBuilder.join(""); 126 | }; 127 | -------------------------------------------------------------------------------- /src/providers/color-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentColorProvider, 3 | ColorInformation, 4 | TextDocument, 5 | Color, 6 | Range, 7 | ColorPresentation, 8 | EndOfLine, 9 | Position, 10 | } from "vscode"; 11 | import { parseToRgb } from "../color-parser"; 12 | import { CACHE } from "../constants"; 13 | import { getActiveRootPath } from "../utils"; 14 | 15 | const getChunkRange = (startLineNumber: number, endLineNumber: number): Range => 16 | new Range(new Position(startLineNumber, 0), new Position(endLineNumber, 0)); 17 | 18 | export class CssColorProvider implements DocumentColorProvider { 19 | async provideDocumentColors( 20 | document: TextDocument 21 | ): Promise { 22 | const eol = document.eol === EndOfLine.CRLF ? "\r\n" : "\n"; 23 | const colorInfo: ColorInformation[] = []; 24 | 25 | // Assuming the worst case that each line has 120 characters (for JS like files) 26 | // 500 lines will contain at-max 500 * 120 = 60000 chars, which is very close to 27 | // 2^16 = 65536 max bytes provided by stream chunks. 28 | const linesRead = 500; 29 | let lineOffset = 0; 30 | let text = document.getText( 31 | getChunkRange(lineOffset, lineOffset + linesRead) 32 | ); 33 | CACHE.activeRootPath = getActiveRootPath(); 34 | 35 | while (text) { 36 | const lines = text.split(eol); 37 | lines.pop(); // Last line will be handled in next loop 38 | lines.forEach((line, lineNumer) => { 39 | const matches = line ? line.matchAll(/var\s*\((.*?)\)/g) : []; 40 | const exactLineNumber = lineNumer + lineOffset; 41 | for (const cssVarMatch of matches) { 42 | const key = cssVarMatch[1].trim(); 43 | const hexColor = CACHE.cssVarsMap[CACHE.activeRootPath][key]?.color; 44 | if (hexColor && cssVarMatch.index) { 45 | const color = parseToRgb(hexColor); 46 | if (color) { 47 | const info = { 48 | color: new Color(color.r, color.g, color.b, color.alpha ?? 1), 49 | range: new Range( 50 | new Position(exactLineNumber, cssVarMatch.index), 51 | new Position(exactLineNumber, cssVarMatch.index + 4) 52 | ), 53 | }; 54 | colorInfo.push(info); 55 | } 56 | } 57 | } 58 | }); 59 | lineOffset += linesRead; 60 | text = document.getText( 61 | getChunkRange(lineOffset, lineOffset + linesRead) 62 | ); 63 | } 64 | return colorInfo; 65 | } 66 | 67 | async provideColorPresentations( 68 | _color: Color, 69 | _context: { document: TextDocument; range: Range } 70 | ): Promise { 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/providers/completion-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CompletionItemProvider, 3 | CompletionList, 4 | Position, 5 | Range, 6 | TextDocument, 7 | } from "vscode"; 8 | import { CACHE, SupportedLanguageIds } from "../constants"; 9 | 10 | import { createCompletionItems } from "../main"; 11 | import { getActiveRootPath, restrictIntellisense } from "../utils"; 12 | 13 | export class CssCompletionProvider implements CompletionItemProvider { 14 | async provideCompletionItems( 15 | document: TextDocument, 16 | position: Position 17 | ): Promise { 18 | const firstInLine = new Position(position.line, 0); 19 | const language: SupportedLanguageIds = 20 | document.languageId as SupportedLanguageIds; 21 | 22 | CACHE.activeRootPath = getActiveRootPath(); 23 | 24 | /** 25 | * VSCode auto-fills extra characters post our current cursor position sometimes 26 | * like typing `var(;` adds `var();` in css files. we can have 2 or more characters 27 | * post our current cursor position. 28 | * 29 | * Taking 5 extra characters make sure we do not miss any extra characters post our cursor 30 | * position, but vscode will take the minimum characters present post cursor position, i.e. 31 | * if there are only 2 more charaters post the cursors position, range will be between (0, n+2) 32 | * not n + 5. 33 | */ 34 | const rangeWithTerminator = new Range( 35 | firstInLine, 36 | position.with(position.line, position.character + 5) 37 | ); 38 | const textFromStart = document.getText(rangeWithTerminator) || ""; 39 | 40 | // Editing Theme File should be restricted 41 | const regions = restrictIntellisense( 42 | textFromStart, 43 | language, 44 | rangeWithTerminator 45 | ); 46 | if (regions.length === 0) { 47 | return null; 48 | } 49 | 50 | const region = regions[regions.length - 1]; 51 | 52 | const completionItems = createCompletionItems( 53 | CACHE.config[CACHE.activeRootPath], 54 | CACHE.cssVars[CACHE.activeRootPath], 55 | { 56 | region, 57 | languageId: language, 58 | } 59 | ); 60 | return new CompletionList(completionItems); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/providers/definition-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Definition, 3 | DefinitionProvider, 4 | LocationLink, 5 | Position, 6 | Range, 7 | TextDocument, 8 | } from "vscode"; 9 | import { CACHE, PATTERN_ALL_VARIABLE_USAGES } from "../constants"; 10 | import { getActiveRootPath } from "../utils"; 11 | 12 | export class CssDefinitionProvider implements DefinitionProvider { 13 | async provideDefinition( 14 | document: TextDocument, 15 | position: Position 16 | ): Promise { 17 | const text = document.getText( 18 | new Range( 19 | position.translate({ characterDelta: -position.character }), 20 | position.with({ line: position.line + 1, character: 0 }) 21 | ) 22 | ); 23 | 24 | const matches = text.matchAll(PATTERN_ALL_VARIABLE_USAGES); 25 | let exactMatch = ""; 26 | for (const match of matches) { 27 | const start = match.index 28 | ? match.index + (match[0].length - match[1].length - 1) 29 | : 0; 30 | const end = start + match[1].length; 31 | if (position.character >= start && position.character <= end) { 32 | exactMatch = match[1].trim(); 33 | break; 34 | } 35 | } 36 | 37 | CACHE.activeRootPath = getActiveRootPath(); 38 | const locations = 39 | CACHE.cssVarDefinitionsMap[CACHE.activeRootPath][exactMatch]; 40 | 41 | return locations; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Diagnostic, 3 | DiagnosticCollection, 4 | DiagnosticSeverity, 5 | ExtensionContext, 6 | Range, 7 | TextDocument, 8 | window, 9 | workspace, 10 | } from "vscode"; 11 | import { 12 | CACHE, 13 | EXTENSION_NAME, 14 | PATTERN_ALL_VARIABLE_USAGES, 15 | } from "../constants"; 16 | 17 | function createDiagnostic( 18 | match: RegExpMatchArray, 19 | lineIndex: number 20 | ): Diagnostic | null { 21 | const variableName = match[1].trim(); 22 | const mode = CACHE.config[CACHE.activeRootPath].mode; 23 | const ignoreRegex = mode[1].ignore; 24 | if ( 25 | CACHE.cssVarsMap[CACHE.activeRootPath][variableName] || 26 | (ignoreRegex && ignoreRegex.test(variableName)) 27 | ) { 28 | return null; 29 | } 30 | 31 | const start = match.index 32 | ? match.index + (match[0].length - match[1].length - 1) 33 | : 0; 34 | const end = start + match[1].length; 35 | const range = new Range(lineIndex, start, lineIndex, end); 36 | 37 | const diagnostic = new Diagnostic( 38 | range, 39 | `Cannot find cssvar ${variableName}.`, 40 | mode[0] === "error" ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning 41 | ); 42 | diagnostic.source = `(${EXTENSION_NAME})`; 43 | 44 | // Following can be used to enhance diagnostics using codeactions 45 | // to modify code if a variable is not present, like adding a variable. 46 | // diagnostic.code = `${EXTENSION_NAME}_mention`; 47 | return diagnostic; 48 | } 49 | 50 | /** 51 | * Analyzes the text document for css variables that 52 | * aren't defined in the source CSS files. 53 | * @param doc text document to analyze 54 | * @param cssvarDiagnostics diagnostic collection 55 | */ 56 | export function refreshDiagnostics( 57 | doc: TextDocument, 58 | cssvarDiagnostics: DiagnosticCollection 59 | ): void { 60 | if ( 61 | !CACHE.config[CACHE.activeRootPath].extensions.includes( 62 | doc.languageId as any 63 | ) || 64 | CACHE.cssVarCount[CACHE.activeRootPath] === 0 || 65 | CACHE.config[CACHE.activeRootPath].mode[0] === "off" 66 | ) { 67 | // This can happen if the extension fails to parse everything 68 | // In which case we do not want to show any diagnostic errors/warns 69 | return; 70 | } 71 | 72 | const diagnostics: Diagnostic[] = []; 73 | 74 | for (let lineIndex = 0; lineIndex < doc.lineCount; lineIndex++) { 75 | const lineOfText = doc.lineAt(lineIndex); 76 | const allMatchesInLine = lineOfText.text.matchAll( 77 | PATTERN_ALL_VARIABLE_USAGES 78 | ); 79 | for (const match of allMatchesInLine) { 80 | const diagnostic = createDiagnostic(match, lineIndex); 81 | if (diagnostic) { 82 | diagnostics.push(diagnostic); 83 | } 84 | } 85 | } 86 | 87 | cssvarDiagnostics.set(doc.uri, diagnostics); 88 | } 89 | 90 | export function subscribeToDocumentChanges( 91 | context: ExtensionContext, 92 | cssvarDiagnostics: DiagnosticCollection 93 | ): void { 94 | if (window.activeTextEditor) { 95 | refreshDiagnostics(window.activeTextEditor.document, cssvarDiagnostics); 96 | } 97 | context.subscriptions.push( 98 | window.onDidChangeActiveTextEditor(editor => { 99 | if (editor) { 100 | refreshDiagnostics(editor.document, cssvarDiagnostics); 101 | } 102 | }) 103 | ); 104 | 105 | context.subscriptions.push( 106 | workspace.onDidChangeTextDocument(e => 107 | refreshDiagnostics(e.document, cssvarDiagnostics) 108 | ) 109 | ); 110 | 111 | context.subscriptions.push( 112 | workspace.onDidCloseTextDocument(doc => cssvarDiagnostics.delete(doc.uri)) 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /src/providers/hover-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CancellationToken, 3 | Hover, 4 | HoverProvider, 5 | MarkdownString, 6 | Position, 7 | Range, 8 | TextDocument, 9 | } from "vscode"; 10 | import { CACHE } from "../constants"; 11 | import { getActiveRootPath } from "../utils"; 12 | 13 | const getMDString = ( 14 | prop: string, 15 | realValue: string, 16 | renderedValue: string, 17 | theme: string 18 | ) => { 19 | const mdString = new MarkdownString("__Variable Details__\n", true); 20 | mdString.appendCodeblock(`${prop}: ${realValue};`, "scss"); 21 | mdString.appendMarkdown( 22 | `- Rendered: \`${renderedValue}\`\n${ 23 | theme !== "" ? `- Theme: [\`${theme}\`]` : "- Theme: _none_" 24 | } \n` 25 | ); 26 | return mdString; 27 | }; 28 | 29 | export class CssHoverProvider implements HoverProvider { 30 | provideHover( 31 | document: TextDocument, 32 | position: Position, 33 | _: CancellationToken 34 | ): Hover | null { 35 | const range = new Range( 36 | position.translate({ characterDelta: -position.character }), 37 | position.with({ line: position.line + 1, character: 0 }) 38 | ); 39 | const text = document.getText(range); 40 | 41 | const matches = text.matchAll(/var\s*\((.*?)\)/g); 42 | let hoverDetails: { 43 | name: string; 44 | range: Range; 45 | } | null = null; 46 | for (const match of matches) { 47 | const start = match.index 48 | ? match.index + (match[0].length - match[1].length - 1) 49 | : 0; 50 | const end = start + match[1].length; 51 | if (position.character >= start && position.character <= end) { 52 | hoverDetails = { 53 | name: match[1].trim(), 54 | range: new Range( 55 | range.start.translate({ characterDelta: start }), 56 | range.start.translate({ characterDelta: end }) 57 | ), 58 | }; 59 | break; 60 | } 61 | } 62 | 63 | if (hoverDetails) { 64 | CACHE.activeRootPath = getActiveRootPath(); 65 | const varDetails = 66 | CACHE.cssVarsMap[CACHE.activeRootPath][hoverDetails.name]; 67 | if (varDetails) { 68 | const content = getMDString( 69 | varDetails.property, 70 | varDetails.real, 71 | varDetails.color || varDetails.value, 72 | varDetails.theme 73 | ); 74 | return new Hover(content, hoverDetails.range); 75 | } 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/remote-paths.ts: -------------------------------------------------------------------------------- 1 | import { createWriteStream, existsSync, mkdirSync, unlink } from "fs"; 2 | import { Readable } from "stream"; 3 | import { http, https } from "follow-redirects"; 4 | import { IncomingMessage } from "http"; 5 | import { URL } from "url"; 6 | import { 7 | createUnzip, 8 | createBrotliDecompress, 9 | Unzip, 10 | BrotliDecompress, 11 | } from "zlib"; 12 | import { version } from "../package.json"; 13 | import { getCachedRemoteFilePath } from "./utils"; 14 | 15 | //#region Get Fetch, replacement for fetch() Browser API 16 | // Once VSCode officially starts supporting Node v17.5+ 17 | // I can migrate this code into using `fetch` api :relaxed: 18 | const CSSGetError = class extends Error { 19 | code = 200; 20 | 21 | constructor(response: IncomingMessage, overrideMsg?: string) { 22 | super(overrideMsg || response.statusMessage || "CSSGetError: "); 23 | 24 | this.name = "CSSGetError"; 25 | this.code = response.statusCode ?? 200; 26 | this.message = 27 | `Response Status Code: ${this.code}, Message: ` + 28 | (overrideMsg ?? response.statusMessage ?? ""); 29 | } 30 | }; 31 | 32 | /** 33 | * This fetch method is tailored to work with only CSS assets. 34 | * If we try to fetch any other type of asset, this method will throw 35 | * It will try to fetch the asset and save it in a temp file as a cache. 36 | * If it fails it will throw a custom error. 37 | * 38 | * @param url Remote URL path for css asset 39 | */ 40 | export const fetchAndCacheAsset = (url: string) => 41 | new Promise((res, rej) => { 42 | const _url = new URL(url); 43 | let httpGet = https.get; 44 | 45 | if (_url.protocol === "http:") { 46 | httpGet = http.get; 47 | } 48 | 49 | httpGet( 50 | _url, 51 | { 52 | headers: { 53 | accept: "application/json, text/plain, */*", 54 | "user-agent": `cssvar/${version}`, 55 | "accept-encoding": "deflate, gzip, br", 56 | }, 57 | }, 58 | message => { 59 | if (message.statusCode !== 200) { 60 | rej(new CSSGetError(message)); 61 | return; 62 | } 63 | if (!/text\/css/.test(message.headers["content-type"] || "")) { 64 | rej(new CSSGetError(message, "Not a CSS request")); 65 | return; 66 | } 67 | 68 | const [parentpath, filepath] = getCachedRemoteFilePath(_url); 69 | 70 | if (!existsSync(parentpath)) { 71 | mkdirSync(parentpath, { recursive: true }); 72 | } 73 | 74 | const data = createWriteStream(filepath, { encoding: "utf-8" }); 75 | const checkFailure = () => { 76 | if (!message.complete) { 77 | /** 78 | * It is important to cleanup/remove cached files if request 79 | * somehow exited before properly writing to the file 80 | */ 81 | unlink(filepath, err => { 82 | if (!err) { 83 | rej( 84 | new CSSGetError( 85 | message, 86 | `Premature request termination: ${message.complete}` 87 | ) 88 | ); 89 | return; 90 | } 91 | rej(err); 92 | }); 93 | } 94 | }; 95 | 96 | const encoding = message.headers["content-encoding"]; 97 | let decompressorPipe: Unzip | BrotliDecompress | null = null; 98 | switch (encoding) { 99 | case "gzip": 100 | case "deflate": 101 | decompressorPipe = createUnzip(); 102 | break; 103 | case "br": 104 | decompressorPipe = createBrotliDecompress(); 105 | break; 106 | default: 107 | } 108 | 109 | let decompressedPipe: Readable = message; 110 | if (decompressorPipe) { 111 | decompressedPipe = message.pipe(decompressorPipe); 112 | } 113 | decompressedPipe.pipe(data); 114 | 115 | message.on("close", checkFailure); 116 | message.on("end", checkFailure); 117 | data.on("close", () => res()); 118 | data.on("error", rej); 119 | } 120 | ).on("error", rej); 121 | }); 122 | //#endregion 123 | -------------------------------------------------------------------------------- /src/scripts/create-changelog.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-require-imports */ 4 | 5 | /** 6 | * Usage: 7 | * `./src/scripts/create-changelog.js v2.1.0...v2.2.0 | pbcopy` 8 | * OR 9 | * `./src/scripts/create-changelog.js v2.1.0..HEAD | pbcopy` 10 | */ 11 | 12 | const { spawn } = require("node:child_process"); 13 | const dayjs = require("dayjs"); 14 | const yargs = require("yargs/yargs"); 15 | const { hideBin } = require("yargs/helpers"); 16 | 17 | const argv = yargs(hideBin(process.argv)).argv; 18 | const compareString = argv._[0]; 19 | 20 | const gitLog = () => 21 | new Promise((res, rej) => { 22 | const contents = []; 23 | /** @type {import("node:child_process").ChildProcessWithoutNullStreams} */ 24 | const git = spawn("git", [ 25 | "shortlog", 26 | compareString, 27 | "--format=%cI -> %s %n %b", 28 | ]); 29 | git.stdout.on("data", function (buf) { 30 | contents.push(buf.toString()); 31 | }); 32 | 33 | git.stderr.on("data", data => { 34 | rej(new Error(data.toString())); 35 | }); 36 | 37 | git.on("close", code => { 38 | if (code !== 0) { 39 | rej(new Error(`Exited with ${code}: Failed to complete git shortlog`)); 40 | } 41 | res(contents.join("")); 42 | }); 43 | }); 44 | 45 | const processCommitMsg = (/** @type {string[]} */ msgTokens) => { 46 | return msgTokens.map(token => { 47 | return token 48 | .trim() 49 | .replace( 50 | /\[?(#(\d+))\]?/, 51 | "[$1](https://github.com/willofindie/vscode-cssvar/issues/$2)" 52 | ); 53 | }); 54 | }; 55 | 56 | (async () => { 57 | try { 58 | const log = await gitLog(); 59 | const [, releaseTag] = compareString.split(/\.\.\.?/); 60 | const filtered = log 61 | .split("\n") 62 | .filter(line => /\d{4}-\d{2}-\d{2}/.test(line)) 63 | .map(line => 64 | line.split("->").map((token, i) => { 65 | const s = token.trim(); 66 | if (i === 0) { 67 | return new Date(s); 68 | } 69 | return s; 70 | }) 71 | ) 72 | .sort((a, b) => +b[0] - +a[0]); 73 | 74 | const lastReleaseDate = dayjs(filtered[0][0]).format("YYYY-MM-DD"); 75 | const changelogs = { 76 | feature: [], 77 | fix: [], 78 | chore: [], 79 | refactor: [], 80 | doc: [], 81 | }; 82 | for (const [, msg] of filtered) { 83 | if (/^v\d+\.\d+\.\d+$/.test(msg)) { 84 | continue; 85 | } 86 | 87 | if (/feat:/.test(msg)) { 88 | changelogs.feature.push(processCommitMsg(msg.split("feat:")).join(" ")); 89 | } else if (/fix:/.test(msg)) { 90 | changelogs.fix.push(processCommitMsg(msg.split("fix:")).join(" ")); 91 | } else if (/chore:/.test(msg)) { 92 | changelogs.chore.push(processCommitMsg(msg.split("chore:")).join(" ")); 93 | } else if (/refactor:/.test(msg)) { 94 | changelogs.refactor.push( 95 | processCommitMsg(msg.split("refactor:")).join(" ") 96 | ); 97 | } else if (/doc:/.test(msg)) { 98 | changelogs.doc.push(processCommitMsg(msg.split("doc:")).join(" ")); 99 | } else { 100 | // Push everything else to `feature`, to track the commits and remove them later. 101 | changelogs.feature.push(msg); 102 | } 103 | } 104 | console.log( 105 | `## [${releaseTag}](https://github.com/willofindie/vscode-cssvar/compare/${compareString}) - ${lastReleaseDate}` 106 | ); 107 | if (changelogs.feature.length > 0) { 108 | console.log("### Features"); 109 | changelogs.feature.forEach(line => console.log(`- ${line}`)); 110 | } 111 | if (changelogs.fix.length > 0) { 112 | console.log("### Fixes"); 113 | changelogs.fix.forEach(line => console.log(`- ${line}`)); 114 | } 115 | if (changelogs.doc.length > 0) { 116 | console.log("### Doc"); 117 | changelogs.doc.forEach(line => console.log(`- ${line}`)); 118 | } 119 | } catch (e) { 120 | console.error(e); 121 | } 122 | })(); 123 | -------------------------------------------------------------------------------- /src/scripts/publish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-require-imports */ 4 | // @ts-check 5 | const { spawn } = require("node:child_process"); 6 | require("dotenv").config(); 7 | 8 | const spawnAsync = (command, ...args) => 9 | new Promise((res, rej) => { 10 | const thread = spawn(command, args, { 11 | env: process.env, 12 | stdio: "inherit", 13 | }); 14 | 15 | thread.on("close", code => { 16 | if (code !== 0) { 17 | rej(code); 18 | } 19 | res(0); 20 | }); 21 | }); 22 | 23 | Promise.all([spawnAsync("yarn", "test"), spawnAsync("yarn", "build")]) 24 | .then(([c1, c2]) => { 25 | console.log("Results: ", c1, c2); 26 | return Promise.allSettled([ 27 | // spawnAsync("vsce", "publish", "--no-dependencies"), 28 | spawnAsync( 29 | "ovsx", 30 | "publish", 31 | "-p", 32 | process.env.OVSX_KEY, 33 | "--no-dependencies" 34 | ), 35 | ]); 36 | }) 37 | .catch(reason => { 38 | console.error(reason); 39 | process.exit(1); 40 | }); 41 | -------------------------------------------------------------------------------- /src/scripts/yarn-berry-list.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-require-imports */ 4 | 5 | const { spawn } = require("node:child_process"); 6 | 7 | async function yarnV2List() { 8 | return new Promise((res, rej) => { 9 | const contents = []; 10 | /** @type {import("node:child_process").ChildProcessWithoutNullStreams} */ 11 | const git = spawn("yarn", ["info", "--recursive --json"]); 12 | git.stdout.on("data", function (buf) { 13 | contents.push(buf.toString()); 14 | }); 15 | 16 | git.stderr.on("data", data => { 17 | rej(new Error(data.toString())); 18 | }); 19 | 20 | git.on("close", code => { 21 | if (code !== 0) { 22 | rej(new Error(`Exited with ${code}: Failed to complete git shortlog`)); 23 | } 24 | res(contents.join("")); 25 | }); 26 | }); 27 | } 28 | 29 | (async () => { 30 | try { 31 | const log = await yarnV2List(); 32 | console.log(log); 33 | } catch (e) { 34 | console.error(e); 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /src/test/at-rules.css: -------------------------------------------------------------------------------- 1 | @layer x01, x02; 2 | @layer x02 { 3 | .test { 4 | color: firebrick; 5 | } 6 | } 7 | @layer x01 { 8 | .test { 9 | color: limegreen; 10 | } 11 | } 12 | 13 | :root { 14 | --red100: #f00; 15 | --red500: #f24455; 16 | } 17 | -------------------------------------------------------------------------------- /src/test/broken.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --blue #333; 3 | } 4 | -------------------------------------------------------------------------------- /src/test/color-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { serializeColor } from "../color-parser"; 2 | 3 | describe("Happy Flows color-parser", () => { 4 | it("can parse transparent", () => { 5 | const result = serializeColor("transparent"); 6 | expect(result.color).toBe("rgba(0, 0, 0, 0)"); 7 | }); 8 | 9 | it("can parse named", () => { 10 | const result = serializeColor("tomato"); 11 | expect(result.color).toBe("rgb(255, 99, 71)"); 12 | }); 13 | 14 | it("can parse hex3, hex4, hex6, hex8", () => { 15 | const result3 = serializeColor("#333"); 16 | expect(result3.color).toBe("rgb(51, 51, 51)"); 17 | const result6 = serializeColor("#333333"); 18 | expect(result6.color).toBe("rgb(51, 51, 51)"); 19 | const result4 = serializeColor("#3333"); 20 | expect(result4.color).toBe("rgba(51, 51, 51, 0.2)"); 21 | const result8 = serializeColor("#33333333"); 22 | expect(result8.color).toBe("rgba(51, 51, 51, 0.2)"); 23 | }); 24 | 25 | it("can parse rgb", () => { 26 | const rgbResult = "rgb(25, 60, 30)"; 27 | const rgbaResult = "rgba(25, 60, 30, 0.5)"; 28 | let result = serializeColor(rgbResult); 29 | expect(result.color).toBe(rgbResult); 30 | result = serializeColor(rgbaResult); 31 | expect(result.color).toBe(rgbaResult); 32 | }); 33 | 34 | it("can parse hsl", () => { 35 | const hslCSS_L3 = "hsl(235, 100%, 50%)"; 36 | const hslCSS_L4 = "hsl(235 100% 50%)"; 37 | const hsla = "hsla(235 100% 50% / .5)"; 38 | const resultL3 = serializeColor(hslCSS_L3); 39 | const resultL4 = serializeColor(hslCSS_L4); 40 | const result = serializeColor(hsla); 41 | expect(resultL3.color).toBe("rgb(0, 21, 255)"); 42 | expect(resultL4.color).toBe("rgb(0, 21, 255)"); 43 | expect(result.color).toBe("rgba(0, 21, 255, 0.5)"); 44 | }); 45 | 46 | it("can parse hwb", () => { 47 | const result = serializeColor("hwb(194 0% 0%)"); 48 | const resultAlpha = serializeColor("hwb(194 0% 0% / .5)"); 49 | expect(result.color).toBe("rgb(0, 195, 255)"); 50 | expect(resultAlpha.color).toBe("rgba(0, 195, 255, 0.5)"); 51 | }); 52 | 53 | it("can parse lch", () => { 54 | const result = serializeColor("lch(52.2345% 72.2 56.2)"); 55 | const resultAlpha = serializeColor("lch(52.2345% 72.2 56.2 / .5)"); 56 | expect(result.color).toBe("rgb(198, 93, 6)"); 57 | expect(resultAlpha.color).toBe("rgba(198, 93, 6, 0.5)"); 58 | }); 59 | 60 | it("can parse lab", () => { 61 | const result = serializeColor("lab(52.2345% 40.1645 59.9971)"); 62 | const resultAlpha = serializeColor("lab(52.2345% 40.1645 59.9971 / .5)"); 63 | expect(result.color).toBe("rgb(198, 93, 6)"); 64 | expect(resultAlpha.color).toBe("rgba(198, 93, 6, 0.5)"); 65 | }); 66 | }); 67 | 68 | describe("Failures color-parser", () => { 69 | it("empty string", () => { 70 | const result = serializeColor(""); 71 | expect(result.color).toBe(""); 72 | }); 73 | 74 | it("wrong hex", () => { 75 | const result = serializeColor("#34345"); 76 | expect(result.isColor).toBeFalsy(); 77 | expect(result.color).toBe("#34345"); 78 | }); 79 | 80 | it("unknown named CSS color", () => { 81 | const result = serializeColor("flamingo"); 82 | expect(result.isColor).toBeFalsy(); 83 | expect(result.color).toBe("flamingo"); 84 | }); 85 | 86 | it("Unsupported CSS4 custom : color() API", () => { 87 | const result = serializeColor("color(display-p3 1 0.5 0)"); 88 | expect(result.isColor).toBeFalsy(); 89 | expect(result.color).toBe("color(display-p3 1 0.5 0)"); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/test/color-provider/color-provider.test.ts: -------------------------------------------------------------------------------- 1 | import { EndOfLine, TextDocument } from "vscode"; 2 | import { parseToRgb } from "../../color-parser"; 3 | 4 | import { CssColorProvider } from "../../providers/color-provider"; 5 | import { CSSVarDeclarations } from "../../main"; 6 | 7 | jest.mock("../../constants", () => { 8 | const DEFAULT_ROOT_FOLDER = "test"; 9 | const CONSTANTS = jest.requireActual("../../constants"); 10 | const VARIABLES_FILE = "path"; 11 | const cssVariables = [ 12 | { 13 | property: "--color-red-300", 14 | value: "#fc8181", 15 | color: "#fc8181", 16 | } as CSSVarDeclarations, 17 | { 18 | property: "--color-red-500", 19 | value: "#e53e3e", 20 | color: "#e53e3e", 21 | } as CSSVarDeclarations, 22 | { 23 | property: "--color-red-700", 24 | value: "#c53030", 25 | color: "#c53030", 26 | } as CSSVarDeclarations, 27 | ]; 28 | 29 | return { 30 | __esModule: true, 31 | ...CONSTANTS, 32 | CACHE: { 33 | ...CONSTANTS.CACHE, 34 | cssVars: { [DEFAULT_ROOT_FOLDER]: { [VARIABLES_FILE]: cssVariables } }, 35 | cssVarsMap: { 36 | [DEFAULT_ROOT_FOLDER]: cssVariables.reduce( 37 | (map, css) => ({ ...map, [css.property]: css }), 38 | {} 39 | ), 40 | }, 41 | activeRootPath: DEFAULT_ROOT_FOLDER, 42 | config: { 43 | [DEFAULT_ROOT_FOLDER]: { 44 | ...CONSTANTS.DEFAULT_CONFIG, 45 | files: [VARIABLES_FILE], 46 | }, 47 | }, 48 | }, 49 | }; 50 | }); 51 | 52 | jest.mock("../../main", () => { 53 | return { 54 | __esModule: true, 55 | setup: jest.fn().mockResolvedValue({}), 56 | }; 57 | }); 58 | 59 | jest.mock("../../parser", () => { 60 | return { 61 | __esModule: true, 62 | parseFiles: jest.fn().mockResolvedValue([]), 63 | }; 64 | }); 65 | 66 | const getDocumentFromText = (text: string) => { 67 | let called = false; 68 | return { 69 | eol: EndOfLine.LF, 70 | getText() { 71 | if (!called) { 72 | called = true; 73 | return `${text}\n\n`; 74 | } 75 | return null; 76 | }, 77 | } as TextDocument; 78 | }; 79 | 80 | describe("Test Color Provider", () => { 81 | let provider: CssColorProvider; 82 | const red300 = parseToRgb("#fc8181"); 83 | const red500 = parseToRgb("#e53e3e"); 84 | 85 | beforeAll(() => { 86 | provider = new CssColorProvider(); 87 | }); 88 | 89 | it("should provide color for single var()", async () => { 90 | const colorInfos = await provider.provideDocumentColors( 91 | getDocumentFromText("color: var(--color-red-300)") 92 | ); 93 | expect(colorInfos.length).toBe(1); 94 | expect(colorInfos[0].color).toMatchObject({ 95 | red: red300!.r, 96 | green: red300!.g, 97 | blue: red300!.b, 98 | alpha: red300!.alpha ?? 1, 99 | }); 100 | expect(colorInfos[0].range.start.character).toBe(7); 101 | expect(colorInfos[0].range.end.character).toBe(11); 102 | }); 103 | it("should provide color for multi-line var()", async () => { 104 | const colorInfos = await provider.provideDocumentColors( 105 | getDocumentFromText( 106 | "color: var(--color-red-300)\ncolor: var(--color-red-500)\ncolor: var(--color-red-700)" 107 | ) 108 | ); 109 | expect(colorInfos.length).toBe(3); 110 | expect(colorInfos[1].color).toMatchObject({ 111 | red: red500!.r, 112 | green: red500!.g, 113 | blue: red500!.b, 114 | alpha: red500!.alpha ?? 1, 115 | }); 116 | expect(colorInfos[1].range).toEqual( 117 | expect.objectContaining({ 118 | start: expect.objectContaining({ 119 | line: 1, 120 | character: 7, 121 | }), 122 | end: expect.objectContaining({ 123 | line: 1, 124 | character: 11, 125 | }), 126 | }) 127 | ); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /src/test/config.test.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from "vscode"; 2 | import { DEFAULT_CONFIG, CACHE, WorkspaceConfig } from "../constants"; 3 | import { setup } from "../main"; 4 | 5 | jest.mock("../constants", () => ({ 6 | ...jest.requireActual("../constants"), 7 | UNSTABLE_FEATURES: { 8 | no_sort: true, 9 | }, 10 | })); 11 | 12 | const DEFAULT_ROOT_FOLDER = "test"; 13 | const wcGet = jest.fn(); 14 | const wcHas = jest.fn(); 15 | 16 | beforeEach(() => { 17 | CACHE.activeRootPath = DEFAULT_ROOT_FOLDER; 18 | CACHE.tmpDir = "foo"; 19 | CACHE.config = {}; 20 | wcGet.mockClear(); 21 | wcHas.mockClear(); 22 | wcHas.mockReturnValue(true); 23 | }); 24 | 25 | beforeAll(() => { 26 | // @ts-expect-error workspaceFolders is readonly 27 | workspace.workspaceFolders = [ 28 | { 29 | uri: { 30 | path: DEFAULT_ROOT_FOLDER, 31 | fsPath: DEFAULT_ROOT_FOLDER, 32 | }, 33 | }, 34 | ]; 35 | 36 | (workspace.getConfiguration).mockImplementation(() => ({ 37 | get: wcGet, 38 | has: wcHas, 39 | })); 40 | }); 41 | 42 | afterAll(() => { 43 | // Reset to default 44 | // @ts-expect-error workspaceFolders is readonly 45 | workspace.workspaceFolders = []; 46 | jest.clearAllMocks(); 47 | }); 48 | 49 | test("should work for boolean unset in config", async () => { 50 | wcHas.mockReturnValue(false); 51 | 52 | const { config } = await setup(); 53 | expect(config[CACHE.activeRootPath].enable).toBeTruthy(); 54 | }); 55 | 56 | test("should work for boolean set to false in config", async () => { 57 | wcGet.mockImplementation((key: keyof WorkspaceConfig) => { 58 | switch (key) { 59 | case "enable": 60 | return false; 61 | default: 62 | return DEFAULT_CONFIG[key]; 63 | } 64 | }); 65 | 66 | const { config } = await setup(); 67 | expect(config[CACHE.activeRootPath].enable).toBeFalsy(); 68 | }); 69 | 70 | test("should work for string array unset in config", async () => { 71 | wcHas.mockReturnValue(false); 72 | 73 | const { config } = await setup(); 74 | expect(config[CACHE.activeRootPath].ignore).toContain("**/node_modules/**"); 75 | }); 76 | 77 | test("should work for string array set to empty in config", async () => { 78 | wcGet.mockImplementation((key: keyof WorkspaceConfig) => { 79 | switch (key) { 80 | case "ignore": 81 | return []; 82 | default: 83 | return DEFAULT_CONFIG[key]; 84 | } 85 | }); 86 | 87 | const { config } = await setup(); 88 | expect(config[CACHE.activeRootPath].ignore.length).toBe(0); 89 | }); 90 | 91 | test("should work for string array set to other values in config", async () => { 92 | wcGet.mockImplementation((key: keyof WorkspaceConfig) => { 93 | switch (key) { 94 | case "ignore": 95 | return ["foo", "bar"]; 96 | default: 97 | return DEFAULT_CONFIG[key]; 98 | } 99 | }); 100 | 101 | const { config } = await setup(); 102 | expect(config[CACHE.activeRootPath].ignore.length).toBe(2); 103 | expect(config[CACHE.activeRootPath].ignore).toContain("foo"); 104 | expect(config[CACHE.activeRootPath].ignore).not.toContain( 105 | "**/node_modules/**" 106 | ); 107 | }); 108 | 109 | test("should convert configs mode, postcssPLugins, postcssSyntax", async () => { 110 | wcGet.mockImplementation((key: keyof WorkspaceConfig) => { 111 | switch (key) { 112 | case "mode": 113 | return "warn"; 114 | case "postcssPlugins": 115 | return ["foo"]; // Old pattern 116 | case "postcssSyntax": 117 | return { "postcss-styled": ["styl", "css"] }; 118 | case "themes": 119 | return null; 120 | default: 121 | return DEFAULT_CONFIG[key]; 122 | } 123 | }); 124 | 125 | const { config } = await setup(); 126 | expect(config[CACHE.activeRootPath].mode).toMatchObject(["warn", {}]); 127 | expect(config[CACHE.activeRootPath].postcssPlugins).toMatchObject([ 128 | ["foo", {}], 129 | ]); 130 | expect(config[CACHE.activeRootPath].postcssSyntax).toMatchObject({ 131 | styl: "postcss-styled", 132 | css: "postcss-styled", 133 | }); 134 | expect(config[CACHE.activeRootPath].themes).toMatchObject([]); 135 | }); 136 | -------------------------------------------------------------------------------- /src/test/css-imports/_f3.scss: -------------------------------------------------------------------------------- 1 | .c { color: red; } 2 | -------------------------------------------------------------------------------- /src/test/css-imports/_f4.scss: -------------------------------------------------------------------------------- 1 | .c { color: red; } 2 | -------------------------------------------------------------------------------- /src/test/css-imports/f1.css: -------------------------------------------------------------------------------- 1 | /* Relative without current dir with extension */ 2 | @import "f3.css" screen and (orientation:landscape); 3 | -------------------------------------------------------------------------------- /src/test/css-imports/f1.scss: -------------------------------------------------------------------------------- 1 | /* @use with current dir without extension and with _ prefix */ 2 | @use "./f3"; 3 | -------------------------------------------------------------------------------- /src/test/css-imports/f2.css: -------------------------------------------------------------------------------- 1 | /* Relative with current dir and no extension */ 2 | @import "./f4"; 3 | -------------------------------------------------------------------------------- /src/test/css-imports/f2.scss: -------------------------------------------------------------------------------- 1 | /* @import without current dir without extension and with _ prefix */ 2 | @import "f4"; 3 | -------------------------------------------------------------------------------- /src/test/css-imports/f3.css: -------------------------------------------------------------------------------- 1 | .c { color: red; } 2 | -------------------------------------------------------------------------------- /src/test/css-imports/f4.css: -------------------------------------------------------------------------------- 1 | .c { color: red; } 2 | -------------------------------------------------------------------------------- /src/test/css-imports/import.css: -------------------------------------------------------------------------------- 1 | /* Relative without current dir */ 2 | @import url("f1.css"); 3 | 4 | /* Relative with current dir */ 5 | @import "./f2.css"; 6 | 7 | /* Url of Parent CSS */ 8 | @import "../renamed.css"; 9 | -------------------------------------------------------------------------------- /src/test/css-imports/import.scss: -------------------------------------------------------------------------------- 1 | // With @use, relative without current dir 2 | @use "f1.scss"; 3 | 4 | // With @import, relative with current dir and extension 5 | @import "./f2.css"; 6 | 7 | /* Import nested file without `_` prefix and without extension */ 8 | @use "nested/f5"; 9 | 10 | @import "nested/f6.scss"; 11 | 12 | :root { 13 | --test-var: #333; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/css-imports/nested/_f5.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willofindie/vscode-cssvar/dd48ea378f580aa840d93f58d257f2abcf54f535/src/test/css-imports/nested/_f5.scss -------------------------------------------------------------------------------- /src/test/css-imports/nested/f6.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willofindie/vscode-cssvar/dd48ea378f580aa840d93f58d257f2abcf54f535/src/test/css-imports/nested/f6.scss -------------------------------------------------------------------------------- /src/test/definition-provider.test.ts: -------------------------------------------------------------------------------- 1 | import { Location, Position, Range, TextDocument, Uri } from "vscode"; 2 | import { CACHE } from "../constants"; 3 | import { CssDefinitionProvider } from "../providers/definition-provider"; 4 | import { TextDocumentStub } from "./test-utilities"; 5 | 6 | jest.mock("../constants", () => { 7 | const DEFAULT_ROOT_FOLDER = "test"; 8 | const CONSTANTS = jest.requireActual("../constants"); 9 | const VARIABLES_FILE = "path"; 10 | 11 | return { 12 | __esModule: true, 13 | ...CONSTANTS, 14 | CACHE: { 15 | ...CONSTANTS.CACHE, 16 | activeRootPath: DEFAULT_ROOT_FOLDER, 17 | config: { 18 | [DEFAULT_ROOT_FOLDER]: { 19 | ...CONSTANTS.DEFAULT_CONFIG, 20 | files: [VARIABLES_FILE], 21 | }, 22 | }, 23 | }, 24 | }; 25 | }); 26 | 27 | jest.mock("../main", () => { 28 | return { 29 | __esModule: true, 30 | setup: jest.fn().mockResolvedValue({}), 31 | }; 32 | }); 33 | jest.mock("../utils", () => { 34 | const DEFAULT_ROOT_FOLDER = "test"; 35 | return { 36 | __esModule: true, 37 | getActiveRootPath: jest.fn().mockReturnValue(DEFAULT_ROOT_FOLDER), 38 | }; 39 | }); 40 | 41 | let defProvider: CssDefinitionProvider | null; 42 | 43 | beforeAll(() => { 44 | defProvider = new CssDefinitionProvider(); 45 | }); 46 | 47 | afterAll(() => { 48 | defProvider = null; 49 | }); 50 | 51 | test("Definition Provider to work with variables present", async () => { 52 | CACHE.cssVarDefinitionsMap = { 53 | [CACHE.activeRootPath]: { 54 | "--color-red-300": [ 55 | new Location( 56 | { 57 | path: "/foo/bar", 58 | } as Uri, 59 | new Range(new Position(0, 3), new Position(0, 18)) 60 | ), 61 | ], 62 | }, 63 | }; 64 | const document = new TextDocumentStub( 65 | "color: var(--color-red-300)" 66 | ) as unknown as TextDocument; 67 | const def = (await defProvider?.provideDefinition( 68 | document, 69 | new Position(0, 14) 70 | )) as Location[]; 71 | 72 | expect(def.length).toBeGreaterThan(0); 73 | expect(def[0].uri).toMatchObject({ path: "/foo/bar" }); 74 | }); 75 | 76 | test("Definition Provider to work w/ var() function with spaces", async () => { 77 | const document = new TextDocumentStub( 78 | "color: var( --color-red-300 )" 79 | ) as unknown as TextDocument; 80 | const def = (await defProvider?.provideDefinition( 81 | document, 82 | new Position(0, 14) 83 | )) as Location[]; 84 | 85 | expect(def.length).toBeGreaterThan(0); 86 | expect(def[0].uri).toMatchObject({ path: "/foo/bar" }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { ExtensionContext, Position, workspace } from "vscode"; 3 | import { DEFAULT_CONFIG, SupportedLanguageIds, CACHE } from "../constants"; 4 | import { activate } from "../extension"; 5 | import { setup } from "../main"; 6 | 7 | const DUMMY_FILE = path.resolve("src", "test", "touch.css"); 8 | 9 | const DEFAULT_ROOT_FOLDER = "test"; 10 | 11 | jest.mock("../constants", () => { 12 | const CONSTANTS = jest.requireActual("../constants"); 13 | return { 14 | __esModule: true, 15 | ...CONSTANTS, 16 | CACHE: { 17 | ...CONSTANTS.CACHE, 18 | activeRootPath: "test", 19 | config: { test: CONSTANTS.DEFAULT_CONFIG }, 20 | }, 21 | }; 22 | }); 23 | jest.mock("../main", () => { 24 | const MAIN = jest.requireActual("../main"); 25 | return { 26 | __esModule: true, 27 | ...MAIN, 28 | setup: jest.fn(), 29 | }; 30 | }); 31 | 32 | const runActivate = async (line: string, id: SupportedLanguageIds) => { 33 | const subscriptions: { 34 | provideCompletionItems: ( 35 | doc: { 36 | getText: () => string; 37 | languageId: SupportedLanguageIds; 38 | }, 39 | position: Position 40 | ) => Promise; 41 | }[] = []; 42 | await activate({ subscriptions } as unknown as ExtensionContext); 43 | return await subscriptions[1].provideCompletionItems( 44 | { getText: () => line, languageId: id }, 45 | new Position(0, line.length) 46 | ); 47 | }; 48 | 49 | describe("Test Extension Activations and Results", () => { 50 | beforeEach(() => { 51 | const _setup = setup as jest.Mock; 52 | _setup.mockImplementation(() => 53 | Promise.resolve().then(() => ({ 54 | config: { 55 | [CACHE.activeRootPath]: { 56 | ...DEFAULT_CONFIG, 57 | files: [DUMMY_FILE], 58 | }, 59 | }, 60 | })) 61 | ); 62 | }); 63 | 64 | beforeAll(() => { 65 | // @ts-expect-error workspaceFolders is readonly 66 | workspace.workspaceFolders = [ 67 | { 68 | uri: { 69 | path: DEFAULT_ROOT_FOLDER, 70 | fsPath: DEFAULT_ROOT_FOLDER, 71 | }, 72 | }, 73 | ]; 74 | }); 75 | 76 | afterAll(() => { 77 | // Reset to Default 78 | // @ts-expect-error workspaceFolders is readonly 79 | workspace.workspaceFolders = []; 80 | }); 81 | 82 | it("should activate", async () => { 83 | const lines = [ 84 | "color: -", 85 | "color: --", 86 | "color: --c", 87 | "-webkit: --", 88 | "-webkit: `--", // CSS-in-JS, should trigger only when two dashes are present 89 | "-webkit: ${`--`}", 90 | "-webkit: ${`--`}", 91 | ]; 92 | expect(await runActivate(lines[0], "css")).not.toBeNull(); 93 | expect(await runActivate(lines[1], "css")).not.toBeNull(); 94 | expect(await runActivate(lines[2], "css")).not.toBeNull(); 95 | expect(await runActivate(lines[3], "css")).not.toBeNull(); 96 | expect(await runActivate(lines[4], "javascript")).not.toBeNull(); 97 | expect(await runActivate(lines[5], "javascriptreact")).not.toBeNull(); 98 | expect(await runActivate(lines[6], "javascriptreact")).not.toBeNull(); 99 | }); 100 | it("should not activate", async () => { 101 | const lines = [ 102 | "-web", // Browser property declarations 103 | "--text", // Variable declaration 104 | "let x = -", // JS 105 | "x--", // JS 106 | "--", // JS 107 | ]; 108 | expect(await runActivate(lines[0], "scss")).toBeNull(); 109 | expect(await runActivate(lines[1], "less")).toBeNull(); 110 | expect(await runActivate(lines[2], "typescript")).toBeNull(); 111 | expect(await runActivate(lines[3], "typescriptreact")).toBeNull(); 112 | expect(await runActivate(lines[4], "javascript")).toBeNull(); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/test/fixtures/edge-cases.jsx: -------------------------------------------------------------------------------- 1 | // ES6 imports 2 | import styled from 'styled'; 3 | import { mod2 } from 'm2'; 4 | 5 | // CJS 6 | const mod3 = require('m3'); 7 | 8 | import { styled } from "@linaria/react"; 9 | 10 | const StyledContainer = styled.div` 11 | :root { 12 | --h1: 44px; 13 | --h2: 32px; 14 | --h3: 24px; 15 | 16 | --color-red: red; 17 | --color-green: green; 18 | --color-blue: blue; 19 | } 20 | `; 21 | 22 | const baseClass = styled.css` 23 | --base-var-1: #333; 24 | 25 | .child { 26 | --child-var-1: #222; 27 | } 28 | `; 29 | 30 | export const RandomComponent = () => { 31 | const classes = [baseClass, "bar"]; 32 | 33 | //#region Possible Edge cases for Template Literal Parsing 34 | /** 35 | * All the below console.logs causes issue, but for now let's neglect it unless someone raises any issue 36 | * This is because, I don't want to officially support JS/TS parsing, as it's a lot of overhead. 37 | */ 38 | // If Ever it comes to fix the below template literal, one of the solution could be to: 39 | // To have a counter for colon and semi-colon, and there count should be at-least one at the 40 | // end of parsing a template literal, to make sure the template-literal is possibly a CSS string. 41 | console.log(`--random: error`); 42 | console.log(`-- foo: ${"foo"}`); 43 | console.log(`--fuzz: ${"fuzz"}`); 44 | console.log(`--flex: `, "flex"); 45 | //#endregion 46 | 47 | console.log(`>>> bob--flex: `, "flex"); 48 | 49 | return ( 50 | 51 |

52 | Hell World 53 |

54 |
55 | ); 56 | }; 57 | 58 | 59 | export { 60 | StyledContainer 61 | }; 62 | -------------------------------------------------------------------------------- /src/test/fixtures/theming.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Variables duplicates */ 3 | --scale-1: 1.125rem; 4 | --scale-2: 1.25rem; 5 | --scale-3: 1.5rem; 6 | --scale-1: 1.875rem; 7 | --scale-2: 2.25rem; 8 | --scale-3: 3rem; 9 | 10 | --brand-primary: #555d6e; 11 | --brand-primary: #4299e1; 12 | } 13 | 14 | :root.dark { 15 | --brand-primary: #ed8936; 16 | } 17 | 18 | :root.dim { 19 | --brand-primary: #e03131; 20 | } 21 | 22 | body { 23 | color: var(--brand-primary); 24 | } 25 | 26 | .dark body { 27 | color: var(--brand-primary); 28 | } 29 | -------------------------------------------------------------------------------- /src/test/main.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range, workspace } from "vscode"; 2 | import { Config, CSSVarRecord, DEFAULT_CONFIG, CACHE } from "../constants"; 3 | import { createCompletionItems } from "../main"; 4 | import { Region } from "../utils"; 5 | import { getLocalCSSVarLocation } from "./test-utilities"; 6 | 7 | jest.mock("../constants", () => ({ 8 | ...jest.requireActual("../constants"), 9 | UNSTABLE_FEATURES: { 10 | no_sort: true, 11 | }, 12 | })); 13 | 14 | let region: Region | null = null; 15 | const DEFAULT_ROOT_FOLDER = "test"; 16 | 17 | beforeEach(() => { 18 | CACHE.activeRootPath = DEFAULT_ROOT_FOLDER; 19 | CACHE.config = { 20 | [CACHE.activeRootPath]: { 21 | ...DEFAULT_CONFIG, 22 | files: [ 23 | { 24 | local: "", 25 | remote: "", 26 | isRemote: false, 27 | }, 28 | ], 29 | postcssPlugins: [], 30 | postcssSyntax: {}, 31 | mode: ["off", {}], 32 | disableSort: false, 33 | }, 34 | } as Record; 35 | }); 36 | 37 | beforeAll(() => { 38 | // @ts-expect-error Readonly property 39 | workspace.workspaceFolders = [ 40 | { 41 | uri: { 42 | path: DEFAULT_ROOT_FOLDER, 43 | fsPath: DEFAULT_ROOT_FOLDER, 44 | }, 45 | }, 46 | ]; 47 | }); 48 | 49 | afterAll(() => { 50 | // @ts-expect-error Readonly property 51 | workspace.workspaceFolders = []; 52 | }); 53 | 54 | describe("Test Extension Main", () => { 55 | beforeEach(() => { 56 | region = { 57 | range: new Range(new Position(0, 5), new Position(0, 10)), 58 | insideVar: false, 59 | suffixChar: "", 60 | } as Region; 61 | }); 62 | 63 | describe(`Test createCompletion method`, () => { 64 | it("Should return CompletionItems with Sorting On", async () => { 65 | const config: Config = { 66 | ...DEFAULT_CONFIG, 67 | files: [ 68 | { 69 | local: "", 70 | remote: "", 71 | isRemote: false, 72 | }, 73 | ], 74 | postcssPlugins: [], 75 | postcssSyntax: {}, 76 | mode: ["off", {}], 77 | disableSort: false, 78 | }; 79 | const cssVars: CSSVarRecord = { 80 | "./src/01.css": [ 81 | { 82 | type: "css", 83 | property: "--red-A100", 84 | value: "red", 85 | real: "red", 86 | theme: "", 87 | }, 88 | ], 89 | "./src/02.css": [ 90 | { 91 | type: "css", 92 | property: "--red-500", 93 | value: "red", 94 | real: "red", 95 | theme: "", 96 | }, 97 | ], 98 | }; 99 | const items = createCompletionItems(config, cssVars, { 100 | region, 101 | languageId: "css", 102 | }); 103 | expect(items[0]).not.toHaveProperty("sortText"); 104 | expect(items[0]).toEqual( 105 | expect.objectContaining({ 106 | insertText: "var(--red-A100);", 107 | }) 108 | ); 109 | }); 110 | it("Should return CompletionItems with Sorting Disabled", async () => { 111 | const config: Config = { 112 | ...DEFAULT_CONFIG, 113 | files: [ 114 | { 115 | local: "", 116 | remote: "", 117 | isRemote: false, 118 | }, 119 | ], 120 | postcssPlugins: [], 121 | postcssSyntax: {}, 122 | mode: ["off", {}], 123 | disableSort: true, 124 | }; 125 | const cssVars: CSSVarRecord = { 126 | "./src/01.css": [ 127 | { 128 | type: "css", 129 | property: "--red-A100", 130 | value: "red", 131 | real: "red", 132 | theme: "", 133 | }, 134 | ], 135 | "./src/02.css": [ 136 | { 137 | type: "css", 138 | property: "--red-500", 139 | value: "red", 140 | real: "red", 141 | theme: "", 142 | }, 143 | ], 144 | }; 145 | 146 | const items = createCompletionItems(config, cssVars, { 147 | region, 148 | languageId: "css", 149 | }); 150 | expect(items[1]).toMatchObject({ 151 | detail: "Value: red", 152 | documentation: "red", 153 | kind: 5, 154 | label: "--red-500", 155 | insertText: "var(--red-500);", 156 | sortText: "1", 157 | }); 158 | }); 159 | it("Should return CompletionItems with 3 and 2digit sortText", async () => { 160 | const config: Config = { 161 | ...DEFAULT_CONFIG, 162 | files: [getLocalCSSVarLocation("")], 163 | postcssPlugins: [], 164 | postcssSyntax: {}, 165 | mode: ["off", {}], 166 | disableSort: true, 167 | }; 168 | const cssVars1: CSSVarRecord = Array(11) 169 | .fill([ 170 | { 171 | property: "--red-A100", 172 | value: "red", 173 | theme: "", 174 | }, 175 | ]) 176 | .reduce((acc, item, index) => { 177 | acc[`./src/${index}.css`] = item; 178 | return acc; 179 | }, {}); 180 | const cssVars2: CSSVarRecord = Array(101) 181 | .fill([ 182 | { 183 | property: "--red-A100", 184 | value: "red", 185 | theme: "", 186 | }, 187 | ]) 188 | .reduce((acc, item, index) => { 189 | acc[`./src/${index}.css`] = item; 190 | return acc; 191 | }, {}); 192 | 193 | const items1 = createCompletionItems(config, cssVars1, { 194 | region, 195 | languageId: "css", 196 | }); 197 | const items2 = createCompletionItems(config, cssVars2, { 198 | region, 199 | languageId: "css", 200 | }); 201 | expect(items1[10]).toMatchObject({ 202 | detail: "Value: red", 203 | documentation: "red", 204 | kind: 5, 205 | label: "--red-A100", 206 | sortText: "10", 207 | }); 208 | expect(items1[9]).toEqual( 209 | expect.objectContaining({ 210 | sortText: "09", 211 | }) 212 | ); 213 | expect(items2[99]).toEqual( 214 | expect.objectContaining({ 215 | sortText: "099", 216 | }) 217 | ); 218 | expect(items2[100]).toEqual( 219 | expect.objectContaining({ 220 | sortText: "100", 221 | }) 222 | ); 223 | }); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /src/test/pre-processors/template-literals.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { JS_BLOCK } from "../../pre-processors"; 3 | import { parseFiles } from "../../parser"; 4 | import { CACHE, Config, CSSVarRecord, DEFAULT_CONFIG } from "../../constants"; 5 | import { getLocalCSSVarLocation } from "../test-utilities"; 6 | 7 | jest.mock("../../constants", () => { 8 | const CONSTANTS = jest.requireActual("../../constants"); 9 | const activeRootPath = "foo"; 10 | return { 11 | __esModule: true, 12 | ...CONSTANTS, 13 | CACHE: { 14 | ...CONSTANTS.CACHE, 15 | activeRootPath, 16 | config: { [activeRootPath]: CONSTANTS.DEFAULT_CONFIG }, 17 | }, 18 | }; 19 | }); 20 | 21 | type ConfigRecord = { [rootPath: string]: Config }; 22 | 23 | const MODIFIED_DATE = new Date("2021-04-12T08:58:58.676Z"); 24 | const JSX_FILE_PATH = resolve(__dirname, "..", "fixtures", "edge-cases.jsx"); 25 | const MOCK_CONFIG: ConfigRecord = { 26 | [CACHE.activeRootPath]: { 27 | ...DEFAULT_CONFIG, 28 | files: [getLocalCSSVarLocation(JSX_FILE_PATH)], 29 | mode: ["off", {}], 30 | postcssPlugins: [], 31 | postcssSyntax: {}, 32 | }, 33 | }; 34 | 35 | const RESULTS = [ 36 | { 37 | type: "css", 38 | property: "--h1", 39 | value: "44px", 40 | theme: "", 41 | }, 42 | { 43 | type: "css", 44 | property: "--h2", 45 | value: "32px", 46 | theme: "", 47 | }, 48 | { 49 | type: "css", 50 | property: "--h3", 51 | value: "24px", 52 | theme: "", 53 | }, 54 | { 55 | type: "css", 56 | property: "--color-red", 57 | value: "red", 58 | theme: "", 59 | color: "rgb(255, 0, 0)", 60 | }, 61 | { 62 | type: "css", 63 | property: "--color-green", 64 | value: "green", 65 | theme: "", 66 | color: "rgb(0, 128, 0)", 67 | }, 68 | { 69 | type: "css", 70 | property: "--color-blue", 71 | value: "blue", 72 | theme: "", 73 | color: "rgb(0, 0, 255)", 74 | }, 75 | { 76 | type: "css", 77 | property: "--base-var-1", 78 | value: "#333", 79 | theme: "", 80 | color: "rgb(51, 51, 51)", 81 | }, 82 | { 83 | type: "css", 84 | property: "--child-var-1", 85 | value: "#222", 86 | theme: "", 87 | color: "rgb(34, 34, 34)", 88 | }, 89 | 90 | // This should belong in WRONG_RESULTS, but for now 91 | // it's fine 92 | { 93 | type: "css", 94 | property: "--random", 95 | value: "error", 96 | theme: "", 97 | }, 98 | ]; 99 | 100 | const WRONG_RESULTS = [ 101 | { 102 | type: "css", 103 | property: "--", 104 | value: JS_BLOCK, 105 | }, 106 | { 107 | type: "css", 108 | property: "--fuzz", 109 | value: JS_BLOCK, 110 | }, 111 | { 112 | type: "css", 113 | property: "--flex", 114 | }, 115 | { 116 | type: "css", 117 | property: "--baz", 118 | }, 119 | ]; 120 | 121 | let globalVarRecord: CSSVarRecord; 122 | beforeAll(() => { 123 | CACHE.filesToWatch[CACHE.activeRootPath] = new Set(); 124 | CACHE.fileMetas = {}; 125 | CACHE.fileMetas[JSX_FILE_PATH] = { 126 | path: JSX_FILE_PATH, 127 | lastModified: +MODIFIED_DATE, 128 | }; 129 | return parseFiles(MOCK_CONFIG).then(([varRecord, _]) => { 130 | globalVarRecord = varRecord; 131 | return varRecord; 132 | }); 133 | }); 134 | 135 | test("should have proper variables present", () => { 136 | for (const result of RESULTS) { 137 | expect(globalVarRecord[JSX_FILE_PATH]).toContainEqual( 138 | expect.objectContaining(result) 139 | ); 140 | } 141 | }); 142 | 143 | test("should not have improper variables", () => { 144 | for (const wrong of WRONG_RESULTS) { 145 | expect(globalVarRecord[JSX_FILE_PATH]).not.toContainEqual( 146 | expect.objectContaining(wrong) 147 | ); 148 | } 149 | }); 150 | -------------------------------------------------------------------------------- /src/test/providers/diagnostics.test.ts: -------------------------------------------------------------------------------- 1 | import { DiagnosticCollection, TextDocument } from "vscode"; 2 | import { CACHE, DEFAULT_CONFIG } from "../../constants"; 3 | import { CSSVarDeclarations } from "../../main"; 4 | import { refreshDiagnostics } from "../../providers/diagnostics"; 5 | import { TextDocumentStub, DiagnosticCollectionStub } from "../test-utilities"; 6 | 7 | type ICache = typeof CACHE; 8 | 9 | jest.mock("../../constants", () => { 10 | const DEFAULT_ROOT_FOLDER = "test"; 11 | const CONSTANTS = jest.requireActual("../../constants"); 12 | const VARIABLES_FILE = "path"; 13 | 14 | return { 15 | __esModule: true, 16 | ...CONSTANTS, 17 | CACHE: { 18 | ...CONSTANTS.CACHE, 19 | activeRootPath: DEFAULT_ROOT_FOLDER, 20 | cssVarsMap: { 21 | [DEFAULT_ROOT_FOLDER]: {}, 22 | }, 23 | config: { 24 | [DEFAULT_ROOT_FOLDER]: { 25 | ...CONSTANTS.DEFAULT_CONFIG, 26 | files: [ 27 | { 28 | local: VARIABLES_FILE, 29 | remote: "", 30 | isRemote: false, 31 | }, 32 | ], 33 | mode: ["off", { ignore: [] }], 34 | }, 35 | }, 36 | } as ICache, 37 | }; 38 | }); 39 | 40 | const resetMockedCache = () => { 41 | const DEFAULT_ROOT_FOLDER = "test"; 42 | const VARIABLES_FILE = "path"; 43 | CACHE.activeRootPath = DEFAULT_ROOT_FOLDER; 44 | CACHE.cssVarsMap[DEFAULT_ROOT_FOLDER] = {}; 45 | CACHE.config = { 46 | [DEFAULT_ROOT_FOLDER]: { 47 | ...DEFAULT_CONFIG, 48 | files: [ 49 | { 50 | local: VARIABLES_FILE, 51 | remote: "", 52 | isRemote: false, 53 | }, 54 | ], 55 | mode: ["off", {}], 56 | postcssPlugins: [], 57 | postcssSyntax: {}, 58 | }, 59 | }; 60 | }; 61 | 62 | beforeEach(() => { 63 | resetMockedCache(); 64 | }); 65 | 66 | test("Should not return diagnostics when turned off", () => { 67 | const docStub = new TextDocumentStub("color: var(--brand-primary);"); 68 | const diagnosticCollectionStub = new DiagnosticCollectionStub("foo"); 69 | 70 | // Update Mocked Cache values: 71 | CACHE.cssVarCount[CACHE.activeRootPath] = 1; 72 | 73 | refreshDiagnostics( 74 | (docStub), 75 | (diagnosticCollectionStub) 76 | ); 77 | 78 | expect(diagnosticCollectionStub.get("foo")).toBe(undefined); 79 | }); 80 | 81 | test("Should return diagnostics when turned on", () => { 82 | CACHE.cssVarsMap[CACHE.activeRootPath] = { 83 | // We don't have variable cached that we are looking for. 84 | "--brand-secondary": {} as unknown as CSSVarDeclarations, 85 | }; 86 | const docStub = new TextDocumentStub(` 87 | color: var(--brand-secondary); 88 | color: var(--brand-primary); 89 | `); 90 | const diagnosticCollectionStub = new DiagnosticCollectionStub("foo"); 91 | 92 | // Update Mocked Cache values: 93 | CACHE.cssVarCount[CACHE.activeRootPath] = 1; 94 | CACHE.config[CACHE.activeRootPath].mode[0] = "error"; 95 | 96 | refreshDiagnostics( 97 | (docStub), 98 | (diagnosticCollectionStub) 99 | ); 100 | 101 | expect(diagnosticCollectionStub.get("foo")).not.toBeNull(); 102 | expect(diagnosticCollectionStub.get("foo").length).toBe(1); 103 | expect(diagnosticCollectionStub.get("foo")[0]).toMatchObject({ 104 | desc: `Cannot find cssvar --brand-primary.`, 105 | }); 106 | }); 107 | 108 | test("Should ignore variables which are added to `ignore` list", () => { 109 | CACHE.cssVarsMap[CACHE.activeRootPath] = { 110 | // We don't have variable cached that we are looking for. 111 | "--brand-secondary": {} as unknown as CSSVarDeclarations, 112 | }; 113 | const docStub = new TextDocumentStub(` 114 | color: var(--brand-secondary); 115 | color: var(--brand-primary); 116 | `); 117 | const diagnosticCollectionStub = new DiagnosticCollectionStub("foo"); 118 | 119 | // Update Mocked Cache values: 120 | CACHE.cssVarCount[CACHE.activeRootPath] = 1; 121 | CACHE.config[CACHE.activeRootPath].mode[0] = "error"; 122 | CACHE.config[CACHE.activeRootPath].mode[1] = { 123 | ignore: new RegExp("--brand-primary"), 124 | }; 125 | 126 | refreshDiagnostics( 127 | (docStub), 128 | (diagnosticCollectionStub) 129 | ); 130 | 131 | // Since the logic runs, an empty set of diagnostics will be set to the collection. 132 | expect(diagnosticCollectionStub.get("foo").length).toBe(0); 133 | }); 134 | -------------------------------------------------------------------------------- /src/test/renamed.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --red100: #f00; 3 | --red500: #f24455; 4 | } 5 | -------------------------------------------------------------------------------- /src/test/test-utilities.ts: -------------------------------------------------------------------------------- 1 | import { CSSVarLocation } from "../constants"; 2 | 3 | export const getLocalCSSVarLocation = (path: string) => 4 | ({ 5 | local: path, 6 | remote: "", 7 | isRemote: false, 8 | }) as CSSVarLocation; 9 | 10 | export class TextDocumentStub { 11 | private document: string; 12 | private _uri: string; 13 | lineCount: number; 14 | lines: string[] = []; 15 | languageId: string; 16 | 17 | constructor(doc: string, uri = "foo") { 18 | this.document = doc; 19 | this.lines = doc.split("\n"); 20 | this.lineCount = this.lines.length; 21 | this._uri = uri; 22 | this.languageId = "css"; 23 | } 24 | 25 | // I have used a getter here, so that I can spy it if I want 26 | get uri() { 27 | return this._uri; 28 | } 29 | 30 | getText = jest.fn().mockImplementation(() => this.document); 31 | lineAt = jest 32 | .fn() 33 | .mockImplementation((index: number) => ({ text: this.lines[index] })); 34 | } 35 | 36 | export class DiagnosticCollectionStub { 37 | private name: string; 38 | private map: Map = new Map(); 39 | 40 | constructor(name: string) { 41 | this.name = name; 42 | } 43 | 44 | set = jest.fn().mockImplementation((uri: string, obj: any) => { 45 | this.map.set(uri, obj); 46 | }); 47 | get = jest.fn().mockImplementation((uri: string) => { 48 | return this.map.get(uri); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/test/touch.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --red: #f00; 3 | } 4 | -------------------------------------------------------------------------------- /src/third-party/index.ts: -------------------------------------------------------------------------------- 1 | import { parse as lessParser } from "postcss-less"; 2 | import { CssExtensions, JsExtensions } from "../constants"; 3 | import scssParser from "./scss/safe-scss-parse"; 4 | import { ProcessOptions } from "postcss"; 5 | 6 | export const getParser = async ( 7 | ext: CssExtensions | JsExtensions 8 | ): Promise => { 9 | switch (ext) { 10 | case "less": 11 | return lessParser as ProcessOptions["parser"]; 12 | default: 13 | return scssParser; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/third-party/safe-parser.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | /*! 3 | * Ref: https://github.com/postcss/postcss-safe-parser/blob/main/lib/safe-parser.js 4 | * I have modified it to work with my extension and use ES6 syntax and 5 | * module system instead. 6 | * 7 | * @license 8 | * Copyright (c) 2013 Andrey Sitnik 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | * the Software, and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | import { Root, Node } from "postcss"; 28 | import type { Token } from "postcss/lib/tokenize"; 29 | import Parser from "postcss/lib/parser"; 30 | 31 | // FIXME(shub) all these failures should change the parsed AST node and remove 32 | // the node from the tree, as they are invalid. 33 | // One possible solution is to call super.method() and wrap it inside 34 | // try/catch block, without overriding these exception methods 35 | // I just need to override `decl()` method with try/catch to make things work 36 | export default abstract class SafeParser extends Parser { 37 | //#region Errors 38 | /** 39 | * This method should only throw if passed tokens contain CSS custom properties 40 | * with prop startsWith `--`, after this get's fixed 41 | */ 42 | unknownWord(tokens: Token[]) { 43 | this.spaces += tokens.map(i => i[1]).join(""); 44 | // console.log("Unknown Word: ", tokens); 45 | } 46 | 47 | unexpectedClose() { 48 | (this.current).raws.after += "}"; 49 | } 50 | 51 | doubleColon() { 52 | return; 53 | } 54 | 55 | unnamedAtrule(node: Node) { 56 | // @ts-ignore 57 | node.name = ""; 58 | } 59 | 60 | precheckMissedSemicolon(tokens: Token[] = []) { 61 | const colon = this.colon(tokens); 62 | if (colon === false) return; 63 | 64 | let nextStart, prevEnd; 65 | for (nextStart = colon - 1; nextStart >= 0; nextStart--) { 66 | if (tokens[nextStart][0] === "word") break; 67 | } 68 | if (nextStart === 0) return; 69 | 70 | for (prevEnd = nextStart - 1; prevEnd >= 0; prevEnd--) { 71 | if (tokens[prevEnd][0] !== "space") { 72 | prevEnd += 1; 73 | break; 74 | } 75 | } 76 | 77 | const other = tokens.slice(nextStart); 78 | const spaces = tokens.slice(prevEnd, nextStart); 79 | tokens.splice(prevEnd, tokens.length - prevEnd); 80 | this.spaces = spaces.map(i => i[1]).join(""); 81 | 82 | this.decl(other); 83 | } 84 | 85 | checkMissedSemicolon(_?: Token[]) { 86 | return; 87 | } 88 | //#endregion 89 | } 90 | -------------------------------------------------------------------------------- /src/third-party/scss/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andrey Sitnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/third-party/scss/nested-declaration.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ref: https://github.com/postcss/postcss-scss 3 | * I have modified it to work with my extension and use ES6 syntax and 4 | * module system instead. 5 | */ 6 | import { Container } from "postcss"; 7 | 8 | export default class NestedDeclaration extends Container { 9 | isNested: boolean; 10 | prop = ""; 11 | important?: boolean; 12 | value: any; 13 | 14 | constructor(defaults?: Record) { 15 | super(defaults); 16 | this.type = "decl"; 17 | this.isNested = true; 18 | if (!this.nodes) this.nodes = []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/third-party/scss/parser.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | 3 | /** 4 | * Ref: https://github.com/postcss/postcss-scss 5 | * I have modified it to work with my extension and use ES6 syntax and 6 | * module system instead. 7 | */ 8 | 9 | import { Comment, Node } from "postcss"; 10 | import type { Token } from "postcss/lib/tokenize"; 11 | import SafeParser from "../safe-parser"; 12 | 13 | import NestedDeclaration from "./nested-declaration"; 14 | import scssTokenizer from "./scss-tokenize"; 15 | 16 | export default class ScssParser extends SafeParser { 17 | createTokenizer() { 18 | this.tokenizer = scssTokenizer(this.input); 19 | } 20 | 21 | rule(tokens: Token[]) { 22 | let withColon = false; 23 | let brackets = 0; 24 | let value = ""; 25 | for (const i of tokens) { 26 | if (withColon) { 27 | if (i[0] !== "comment" && i[0] !== "{") { 28 | value += i[1]; 29 | } 30 | } else if (i[0] === "space" && i[1]?.includes("\n")) { 31 | break; 32 | } else if (i[0] === "(") { 33 | brackets += 1; 34 | } else if (i[0] === ")") { 35 | brackets -= 1; 36 | } else if (brackets === 0 && i[0] === ":") { 37 | withColon = true; 38 | } 39 | } 40 | 41 | if (!withColon || value.trim() === "" || /^[#:A-Za-z-]/.test(value)) { 42 | super.rule(tokens); 43 | } else { 44 | tokens.pop(); 45 | const node = new NestedDeclaration(); 46 | this.init(node, tokens[0][2]); 47 | 48 | let last = [] as unknown as Token; 49 | for (let i = tokens.length - 1; i >= 0; i--) { 50 | if (tokens[i][0] !== "space") { 51 | last = tokens[i]; 52 | break; 53 | } 54 | } 55 | 56 | if (last[3]) { 57 | const pos = this.input.fromOffset(last[3]); 58 | node.source!.end = { 59 | offset: last[3], 60 | line: pos!.line, 61 | column: pos!.col, 62 | }; 63 | } else { 64 | const pos = this.input.fromOffset(last[2] || 0); 65 | node.source!.end = { 66 | offset: last[2] || 0, 67 | line: pos!.line, 68 | column: pos!.col, 69 | }; 70 | } 71 | 72 | while (tokens[0][0] !== "word") { 73 | node.raws.before += tokens.shift()![1]; 74 | } 75 | 76 | if (tokens[0][2]) { 77 | const pos = this.input.fromOffset(tokens[0][2]); 78 | node.source!.start = { 79 | offset: tokens[0][2], 80 | line: pos!.line, 81 | column: pos!.col, 82 | }; 83 | } 84 | 85 | node!.prop = ""; 86 | while (tokens.length) { 87 | const type = tokens[0][0]; 88 | // @ts-ignore 89 | if (type === ":" || type === "space" || type === "comment") { 90 | break; 91 | } 92 | node.prop += tokens.shift()![1]; 93 | } 94 | 95 | node.raws.between = ""; 96 | 97 | let token; 98 | while (tokens.length) { 99 | token = tokens.shift()!; 100 | 101 | if (token[0] === ":") { 102 | node.raws.between += token[1]; 103 | break; 104 | } else { 105 | node.raws.between += token[1]; 106 | } 107 | } 108 | 109 | if (node.prop[0] === "_" || node.prop[0] === "*") { 110 | node.raws.before += node.prop[0]; 111 | node.prop = node.prop.slice(1); 112 | } 113 | node.raws.between += this.spacesAndCommentsFromStart(tokens); 114 | this.precheckMissedSemicolon(tokens); 115 | 116 | for (let i = tokens.length - 1; i > 0; i--) { 117 | token = tokens[i]; 118 | if (token[1] === "!important") { 119 | node.important = true; 120 | let string = this.stringFrom(tokens, i); 121 | string = this.spacesFromEnd(tokens) + string; 122 | if (string !== " !important") { 123 | node.raws.important = string; 124 | } 125 | break; 126 | } else if (token[1] === "important") { 127 | const cache = tokens.slice(0); 128 | let str = ""; 129 | for (let j = i; j > 0; j--) { 130 | const type = cache[j][0]; 131 | if (str.trim().indexOf("!") === 0 && type !== "space") { 132 | break; 133 | } 134 | str = cache.pop()![1] + str; 135 | } 136 | if (str.trim().indexOf("!") === 0) { 137 | node.important = true; 138 | node.raws.important = str; 139 | tokens = cache; 140 | } 141 | } 142 | 143 | if (token[0] !== "space" && token[0] !== "comment") { 144 | break; 145 | } 146 | } 147 | 148 | this.raw(node, "value", tokens); 149 | 150 | if (node.value.includes(":")) { 151 | this.checkMissedSemicolon(tokens); 152 | } 153 | 154 | this.current = node; 155 | } 156 | } 157 | 158 | comment(token: Token) { 159 | if (token[4] === "inline") { 160 | const node = new Comment(); 161 | this.init(node, token[2]); 162 | node.raws.inline = true; 163 | const pos = this.input.fromOffset(token[3] || 0); 164 | node.source!.end = { 165 | offset: token[3]!, 166 | line: pos!.line, 167 | column: pos!.col, 168 | }; 169 | 170 | const text = token[1]!.slice(2); 171 | if (/^\s*$/.test(text)) { 172 | node.text = ""; 173 | node.raws.left = text; 174 | node.raws.right = ""; 175 | } else { 176 | const match = text.match(/^(\s*)([^]*\S)(\s*)$/)!; 177 | const fixed = match[2].replace(/(\*\/|\/\*)/g, "*//*"); 178 | node.text = fixed; 179 | node.raws.left = match[1]; 180 | node.raws.right = match[3]; 181 | node.raws.text = match[2]; 182 | } 183 | } else { 184 | super.comment(token); 185 | } 186 | } 187 | 188 | atrule(token: Token) { 189 | let name = token[1] || ""; 190 | let prev = token; 191 | while (!this.tokenizer.endOfFile()) { 192 | const next = this.tokenizer.nextToken()!; 193 | if (next[0] === "word" && next[2] === prev[3]! + 1) { 194 | name += next[1]; 195 | prev = next; 196 | } else { 197 | this.tokenizer.back(next); 198 | break; 199 | } 200 | } 201 | 202 | super.atrule(["at-word", name, token[2], prev[3]]); 203 | } 204 | 205 | raw(node: Node, prop: string, tokens: Token[], customProperty?: any) { 206 | super.raw(node, prop, tokens, customProperty); 207 | if (node.raws[prop]) { 208 | const scss = node.raws[prop].raw; 209 | node.raws[prop].raw = tokens.reduce((all, i) => { 210 | if (i[0] === "comment" && i[4] === "inline") { 211 | const text = i[1]?.slice(2).replace(/(\*\/|\/\*)/g, "*//*"); 212 | return all + "/*" + text + "*/"; 213 | } else { 214 | return all + i[1]; 215 | } 216 | }, ""); 217 | if (scss !== node.raws[prop].raw) { 218 | node.raws[prop].scss = scss; 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/third-party/scss/safe-scss-parse.ts: -------------------------------------------------------------------------------- 1 | import { Input, ProcessOptions } from "postcss"; 2 | import type { Root } from "postcss"; 3 | import ScssParser from "./parser"; 4 | 5 | export default function scssParse( 6 | scss: string | { toString(): string }, 7 | opts?: Pick 8 | ): Root { 9 | const inputString = typeof scss === "string" ? scss : scss.toString(); 10 | const input = new Input(inputString, opts); 11 | 12 | const parser = new ScssParser(input); 13 | parser.parse(); 14 | 15 | return parser.root; 16 | } 17 | -------------------------------------------------------------------------------- /src/unstable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enable Unstable Features, if user has added cofig for the same. 3 | */ 4 | 5 | import { CompletionItem } from "vscode"; 6 | import { Config } from "./constants"; 7 | 8 | export const disableDefaultSort = ( 9 | config: Config, 10 | item: CompletionItem, 11 | options: { 12 | size: number; 13 | index: number; 14 | } 15 | ) => { 16 | if (config.disableSort) { 17 | const padSize = Math.floor(Math.log(options.size - 1) / Math.log(10) + 1); 18 | item.sortText = `${options.index}`.padStart(padSize, "0"); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "outDir": "out", 6 | "lib": [ 7 | "esnext" 8 | ], 9 | "sourceMap": true, 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "strictNullChecks": true, 13 | "resolveJsonModule": true 14 | }, 15 | "include": [ 16 | "src", 17 | "@types" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test", 22 | "out" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------