├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.yml ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .prettierrc.yml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── arbitrary.twin ├── colors.js ├── package.json ├── pnpm-lock.yaml ├── tailwind.config.ts ├── theme.ts ├── tsconfig.json └── utopia.ts ├── examples └── README.md ├── jest.config.js ├── media ├── icon.png ├── icon.svg └── icon.xcf ├── package.json ├── package.nls.json ├── package.nls.zh-tw.json ├── pnpm-lock.yaml ├── preview.gif ├── src ├── client.ts ├── colorPresentations.ts ├── common │ ├── color.ts │ ├── culori.ts │ ├── extractors │ │ ├── default.ts │ │ ├── raw.ts │ │ ├── types.ts │ │ └── typescript.ts │ ├── get_set.ts │ ├── idebounce.ts │ ├── index.ts │ ├── logger.ts │ ├── markdown.ts │ ├── module.ts │ ├── parser │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ ├── hover.spec.ts.snap │ │ │ │ └── spread.spec.ts.snap │ │ │ ├── hover.spec.ts │ │ │ ├── spread.spec.ts │ │ │ └── theme.spec.ts │ │ ├── hover.ts │ │ ├── index.ts │ │ ├── nodes.ts │ │ ├── parse_regexp.ts │ │ ├── parse_theme.ts │ │ ├── spread.ts │ │ ├── suggest.ts │ │ ├── theme.ts │ │ └── util.ts │ ├── plugins │ │ ├── _base.ts │ │ ├── _parse.ts │ │ ├── backgrounds.ts │ │ ├── borders.ts │ │ ├── content.ts │ │ ├── effects.ts │ │ ├── filters.ts │ │ ├── flexbox.ts │ │ ├── index.ts │ │ ├── interactivity.ts │ │ ├── layout.ts │ │ ├── plugins.ts │ │ ├── sizing.ts │ │ ├── spacing.ts │ │ ├── svg.ts │ │ ├── tables.ts │ │ ├── tests │ │ │ ├── core.spec.ts │ │ │ ├── flattenColorPalette.spec.ts │ │ │ └── index.spec.ts │ │ ├── transforms.ts │ │ ├── transitions.ts │ │ └── typography.ts │ ├── pnp.ts │ ├── unquote.ts │ └── vscode-css-languageservice │ │ └── index.ts ├── extension.ts ├── locale.ts ├── service.ts ├── service │ ├── colorProvider.ts │ ├── completion.ts │ ├── completionResolve.ts │ ├── diagnostics.ts │ ├── docs.yaml │ ├── documentColors.ts │ ├── hover.ts │ ├── referenceLink.ts │ ├── references.yaml │ └── tailwind │ │ ├── data.ts │ │ ├── index.ts │ │ ├── tw.ts │ │ └── twin.ts ├── shared.ts ├── tsconfig.json └── typings │ ├── completion.d.ts │ ├── culori.d.ts │ ├── global.d.ts │ ├── module.d.ts │ └── vscode-css-languageservice │ └── lib │ ├── VERSION.md │ └── esm │ ├── data │ └── webCustomData.d.ts │ ├── languageFacts │ ├── builtinData.d.ts │ ├── colors.d.ts │ ├── dataManager.d.ts │ ├── dataProvider.d.ts │ ├── entry.d.ts │ └── facts.d.ts │ ├── parser │ ├── cssErrors.d.ts │ ├── cssNodes.d.ts │ ├── cssParser.d.ts │ ├── cssScanner.d.ts │ ├── cssSymbolScope.d.ts │ ├── lessParser.d.ts │ ├── lessScanner.d.ts │ ├── scssErrors.d.ts │ ├── scssParser.d.ts │ └── scssScanner.d.ts │ └── services │ ├── cssCodeActions.d.ts │ ├── cssCompletion.d.ts │ ├── cssFolding.d.ts │ ├── cssHover.d.ts │ ├── cssNavigation.d.ts │ ├── cssSelectionRange.d.ts │ ├── cssValidation.d.ts │ ├── lessCompletion.d.ts │ ├── lint.d.ts │ ├── lintRules.d.ts │ ├── lintUtil.d.ts │ ├── pathCompletion.d.ts │ ├── scssCompletion.d.ts │ ├── scssNavigation.d.ts │ └── selectorPrinting.d.ts ├── syntaxes ├── injection.json ├── injectionCs.json ├── injectionTag.json ├── language-configuration.json ├── scss.tmLanguage.json └── twin.json ├── tsconfig.json ├── webpack.analyzer.js ├── webpack.analyzer.ts ├── webpack.config.js └── webpack.config.ts /.browserslistrc: -------------------------------------------------------------------------------- 1 | maintained node versions 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | charset = utf-8 11 | indent_style = tab 12 | indent_size = 4 13 | max_line_length = 80 14 | 15 | [*.{ts,js}] 16 | max_line_length = 120 17 | 18 | [*.{json,yaml,yml}] 19 | max_line_length = 80 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | webpack.config.js 3 | webpack.analyzer.js 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | parser: "@typescript-eslint/parser" 3 | plugins: 4 | - "@typescript-eslint" 5 | extends: 6 | - "eslint:recommended" 7 | - "plugin:@typescript-eslint/eslint-recommended" 8 | - "plugin:@typescript-eslint/recommended" 9 | parserOptions: 10 | sourceType: module 11 | ignorePatterns: 12 | - "src/typings/*" 13 | rules: 14 | spaced-comment: error 15 | no-var: error 16 | no-extra-bind: error 17 | prefer-arrow-callback: error 18 | no-empty: 19 | - warn 20 | - allowEmptyCatch: true 21 | "@typescript-eslint/ban-ts-comment": off 22 | "@typescript-eslint/no-unused-vars": 23 | - warn 24 | - vars: all 25 | args: none 26 | ignoreRestSiblings: true 27 | caughtErrors: none 28 | "@typescript-eslint/no-empty-interface": off 29 | "@typescript-eslint/camelcase": off 30 | "@typescript-eslint/explicit-function-return-type": off 31 | "@typescript-eslint/array-type": 32 | - off 33 | "@typescript-eslint/no-var-requires": warn 34 | "@typescript-eslint/member-delimiter-style": 35 | - error 36 | - multiline: 37 | delimiter: none 38 | singleline: 39 | delimiter: semi 40 | requireLast: false 41 | "@typescript-eslint/explicit-module-boundary-types": off 42 | 43 | env: 44 | node: true 45 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test 5 | *.vsix 6 | *.tsbuildinfo 7 | statistics.html 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightyen/vscode-tailwindcss-twin/344ee67f77a640ffb7458b1ebab895abcff65b79/.gitmodules -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | endOfLine: lf 2 | printWidth: 120 3 | useTabs: true 4 | semi: false 5 | singleQuote: false 6 | jsxSingleQuote: false 7 | arrowParens: avoid 8 | trailingComma: all 9 | overrides: 10 | - files: "**/(*.json|*.yaml|*.yml|.babelrc|.prettierrc|.eslintrc)" 11 | options: 12 | useTabs: false 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "naumovs.color-highlight", 4 | "dbaeumer.vscode-eslint", 5 | "christian-kohler.npm-intellisense", 6 | "esbenp.prettier-vscode", 7 | "editorconfig.editorconfig", 8 | "visualstudioexptteam.vscodeintellicode" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Client", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 11 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 12 | "preLaunchTask": { 13 | "type": "npm", 14 | "script": "watch" 15 | } 16 | }, 17 | { 18 | "name": "Attach to Server", 19 | "type": "node", 20 | "request": "attach", 21 | "port": 6009, 22 | "address": "localhost", 23 | "protocol": "inspector", 24 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 25 | } 26 | ], 27 | "compounds": [ 28 | { 29 | "name": "Client + Server", 30 | "configurations": ["Launch Client", "Attach to Server"] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.suggest.showSnippets": false, 3 | "emmet.showAbbreviationSuggestions": false, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": true 6 | }, 7 | "editor.formatOnSave": true, 8 | "formatFiles.excludePattern": "node_modules,out,.gitignore,.editorconfig,yarn.lock", 9 | "search.exclude": { 10 | "dist/**": true, 11 | "examples/**": true, 12 | "node_modules/**": true, 13 | "yarn.lock": true 14 | }, 15 | "typescript.tsdk": "node_modules/typescript/lib", 16 | "npm.autoDetect": "off", 17 | "typescript.tsc.autoDetect": "off", 18 | "eslint.validate": ["javascript", "typescript"], 19 | "files.watcherExclude": { 20 | "**/dist": true 21 | }, 22 | "editor.defaultFormatter": "esbenp.prettier-vscode", 23 | "[json]": { 24 | "editor.defaultFormatter": "esbenp.prettier-vscode" 25 | }, 26 | "[jsonc]": { 27 | "editor.defaultFormatter": "esbenp.prettier-vscode" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "watch", 7 | "isBackground": true, 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "panel": "dedicated", 14 | "reveal": "never" 15 | }, 16 | "problemMatcher": [ 17 | { 18 | "pattern": [ 19 | { 20 | "regexp": "(ERROR|WARNING)\\s*in\\s*(.*)?:(\\d+):(\\d+)\\s+(.*)", 21 | "severity": 1, 22 | "file": 2, 23 | "line": 3, 24 | "column": 4, 25 | "message": 5 26 | } 27 | ], 28 | "background": { 29 | "beginsPattern": "cross-env NODE_ENV=development", 30 | "endsPattern": "webpack \\d+\\.\\d+\\.\\d+ compiled", 31 | "activeOnStart": true 32 | } 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | webpack.config.* 3 | **/*.ts 4 | .gitignore 5 | .prettierrc.yml 6 | .editorconfig 7 | .eslintignore 8 | webpack.tsconfig.json 9 | **/tsconfig.json 10 | **/tsconfig.base.json 11 | contributing.md 12 | tests/** 13 | references 14 | .github/** 15 | examples 16 | media/icon.svg 17 | media/icon.xcf 18 | # node_modules 19 | src/**/* 20 | .browserslistrc 21 | statistics.html 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lightyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind Twin IntelliSense 2 | 3 | This is a VSCode Tailwind IntelliSense Extension which supports [twin.macro](https://github.com/ben-rogerson/twin.macro) 4 | 5 | [Install via the Marketplace](https://marketplace.visualstudio.com/items?itemName=lightyen.tailwindcss-intellisense-twin) 6 | 7 | ![preview](preview.gif) 8 | 9 | ## Features 10 | 11 | - auto completion 12 | - hover 13 | - color decoration 14 | - document references 15 | - diagnostics 16 | 17 | ## Pack 18 | 19 | ```sh 20 | pnpm install && pnpm package 21 | ``` 22 | 23 | ## VSCode Settings 24 | 25 | ### Recommended 26 | 27 | ```json5 28 | { 29 | // none 30 | } 31 | ``` 32 | 33 | ### Defaults 34 | 35 | ```json5 36 | { 37 | "tailwindcss.colorDecorators": "inherit", // inherit from "editor.colorDecorators" 38 | "tailwindcss.references": true, 39 | "tailwindcss.diagnostics": { 40 | "enabled": true, 41 | "emptyChecking": true, 42 | }, 43 | "tailwindcss.preferVariantWithParentheses": false, 44 | "tailwindcss.fallbackDefaultConfig": true, 45 | "tailwindcss.enabled": true, 46 | "tailwindcss.jsxPropImportChecking": true, 47 | "tailwindcss.rootFontSize": 16, 48 | "tailwindcss.logLevel": "info", 49 | "tailwindcss.hoverColorHint": "none", 50 | "tailwindcss.otherLanguages": [] 51 | } 52 | ``` 53 | 54 | ### Custom CompletionList Panel 55 | 56 | ```json5 57 | // example 58 | { 59 | "workbench.colorCustomizations": { 60 | "[One Dark Pro Darker]": { 61 | "editorHoverWidget.background": "#1f2229e8", 62 | "editorSuggestWidget.background": "#1f2229e8", 63 | "editor.wordHighlightBackground": "#0000", 64 | "editor.wordHighlightBorder": "#3f3f3f3d", 65 | "editor.wordHighlightStrongBorder": "#3f3f3f3d" 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### Custom Semantic Colors [(docs)](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide) 72 | 73 | ```json5 74 | { 75 | "editor.tokenColorCustomizations": { 76 | "[One Dark Pro Darker]": { 77 | "textMateRules": [ 78 | { 79 | "scope": "support.constant.classname.twin", 80 | "settings": { 81 | "foreground": "#7ddb89" 82 | } 83 | }, 84 | { 85 | "scope": "entity.other.inherited-class.variant.twin", 86 | "settings": { 87 | "foreground": "#c678dd" 88 | } 89 | }, 90 | { 91 | "scope": "support.type.short-css.prop.twin", 92 | "settings": { 93 | "foreground": "#5dbeff" 94 | } 95 | }, 96 | { 97 | "scope": "punctuation.section.embedded.short-css", 98 | "settings": { 99 | "foreground": "#5dbeff" 100 | } 101 | }, 102 | { 103 | "scope": "support.type.arbitrary-style.prop.twin", 104 | "settings": { 105 | "foreground": "#8a88fc" 106 | } 107 | }, 108 | { 109 | "scope": "punctuation.section.embedded.arbitrary-style", 110 | "settings": { 111 | "foreground": "#8a88fc" 112 | } 113 | }, 114 | { 115 | "scope": "entity.name.variable.css-value.twin", 116 | "settings": { 117 | "foreground": "#abb2bb" 118 | } 119 | } 120 | ] 121 | } 122 | } 123 | } 124 | ``` 125 | -------------------------------------------------------------------------------- /demo/arbitrary.twin: -------------------------------------------------------------------------------- 1 | tab-[bat]:uppercase 2 | tab-[bat]:uppercase 3 | divide-[gray] 4 | bg-[gray] 5 | from-[gray] 6 | via-[gray] 7 | to-[gray] 8 | placeholder-[gray] 9 | accent-[gray] 10 | space-x-[1.5px] 11 | space-y-[1.5px] 12 | divide-x-[1.5px] 13 | divide-y-[1.5px] 14 | w-[10rem] 15 | h-[10rem] 16 | leading-[1rem] 17 | m-[1rem] 18 | mx-[1rem] 19 | my-[1rem] 20 | mt-[1rem] 21 | mr-[1rem] 22 | mb-[1rem] 23 | ml-[1rem] 24 | p-[1rem] 25 | px-[1rem] 26 | py-[1rem] 27 | pt-[1rem] 28 | pr-[1rem] 29 | pb-[1rem] 30 | pl-[1rem] 31 | max-w-[10rem] 32 | max-h-[10rem] 33 | inset-[10rem] 34 | inset-x-[10rem] 35 | inset-y-[10rem] 36 | top-[10rem] 37 | right-[10rem] 38 | left-[10rem] 39 | bottom-[10rem] 40 | gap-[1rem] 41 | translate-x-[1rem] 42 | translate-y-[1rem] 43 | blur-[1rem] 44 | backdrop-blur-[1rem] 45 | bg-opacity-[1] 46 | text-opacity-[1] 47 | divide-opacity-[1] 48 | border-opacity-[1] 49 | placeholder-opacity-[1] 50 | ring-opacity-[1] 51 | order-[1] 52 | flex-[1] 53 | scale-[1] 54 | scale-x-[1] 55 | scale-y-[1] 56 | opacity-[1] 57 | brightness-[1] 58 | contrast-[1] 59 | grayscale-[1] 60 | saturate-[1] 61 | sepia-[1] 62 | backdrop-opacity-[1] 63 | backdrop-brightness-[1] 64 | backdrop-contrast-[1] 65 | backdrop-grayscale-[1] 66 | backdrop-saturate-[1] 67 | backdrop-sepia-[1] 68 | rotate-[1deg] 69 | skew-x-[1deg] 70 | skew-y-[1deg] 71 | hue-rotate-[1deg] 72 | backdrop-hue-rotate-[1deg] 73 | border-[1px] 74 | border-[gray] 75 | border-t-[1px] 76 | border-t-[gray] 77 | border-r-[1px] 78 | border-r-[gray] 79 | border-b-[1px] 80 | border-b-[gray] 81 | border-l-[1px] 82 | border-l-[gray] 83 | border-x-[1px] 84 | border-x-[1px] 85 | border-y-[gray] 86 | border-y-[gray] 87 | border-spacing-[1px] 88 | border-spacing-x-[1px] 89 | border-spacing-y-[1px] 90 | text-[1px] 91 | text-[gray] 92 | ring-[1px] 93 | ring-[gray] 94 | ring-offset-[1px] 95 | ring-offset-[gray] 96 | stroke-[1px] 97 | stroke-[gray] 98 | indent-[2px] 99 | duration-[231ms] 100 | delay-[231ms] 101 | transition-[all] 102 | columns-[33] 103 | ease-[cubic-bezier(0.25,0.1,0.25,1.0)] 104 | grid-cols-[repeat(1,minmax(0,1fr))] 105 | grid-cols-[[linename],1fr,auto] 106 | grid-rows-[repeat(1,minmax(0,1fr))] 107 | content-['sdf'] 108 | bg-[#000000] 109 | bg-[url(img_flwr.gif), url(paper.gif)] 110 | text-[#000000] 111 | text-[1px] 112 | border-[#333333] 113 | border-[2cm] 114 | border-t-[transparent] 115 | border-t-[2rem] 116 | border-r-[rgb(3 3 3 / 0.2)] 117 | border-r-[1em] 118 | border-b-[#3213] 119 | border-b-[5px] 120 | border-l-[black] 121 | border-l-[6ch] 122 | ring-[#353213] 123 | ring-[1px] 124 | ring-offset-[green] 125 | ring-offset-[1px] 126 | stroke-[2px] 127 | stroke-[hsl(268 40% 24% / .7)] 128 | animate-[spin] 129 | fill-[red] 130 | stroke-[red] 131 | shadow-[#353213] 132 | underline-offset-[1.5px] 133 | decoration-[red] 134 | outline-[0] 135 | outline-[red] 136 | border-top-width[2rem] 137 | [border-top-width: 2rem] 138 | align-[4px] 139 | -mt-[5px] 140 | -align-[-10px] 141 | -object-[var(--whatever)] 142 | -outline-offset-[3px] 143 | col-start-[1] 144 | test-[red] 145 | screen-[333px]: 146 | sm:marker:peer-hover:tab-[ab]:foo:mdx:col-span-3 147 | accent-[theme( colors.foo-5/10 )] 148 | -------------------------------------------------------------------------------- /demo/colors.js: -------------------------------------------------------------------------------- 1 | exports.bar = "rgb(var(--color) / )" 2 | exports.qoo = function () { 3 | return "rgb(var(--color))" 4 | } 5 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "demo", 4 | "devDependencies": { 5 | "@types/node": "*", 6 | "daisyui": "*", 7 | "postcss": "*", 8 | "autoprefixer": "*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /demo/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { bar, qoo } from "./colors" 2 | import utopia, { fontSize } from "./utopia" 3 | 4 | export default { 5 | lightMode: "media", 6 | theme: { 7 | extend: { 8 | tabSize: { 9 | px: "1px", 10 | }, 11 | colors: { 12 | bar, 13 | qoo, 14 | "foo-5": "#056660", 15 | "foo-5/10": "#051060", 16 | "foo-5/10/10%": "#651025", 17 | space: { 18 | "1/1": "#051025", 19 | }, 20 | }, 21 | }, 22 | }, 23 | experimental: { matchVariant: true }, 24 | plugins: [ 25 | utopia(fontSize()), 26 | function ({ addVariant }) { 27 | addVariant("foo", ({ container }) => { 28 | container.walkRules(rule => { 29 | rule.selector = `.foo\\:${rule.selector.slice(1)}` 30 | rule.walkDecls(decl => { 31 | decl.important = true 32 | }) 33 | }) 34 | }) 35 | }, 36 | function ({ addVariant, postcss }) { 37 | addVariant("mdx", [ 38 | ({ container }) => { 39 | const mediaRule1 = postcss.atRule({ 40 | name: "media", 41 | params: "(min-width: 1200px)", 42 | }) 43 | mediaRule1.append(container.nodes) 44 | container.push(mediaRule1) 45 | mediaRule1.walkRules(rule => { 46 | rule.selector = `.mdx\\:${rule.selector.slice(1)}` 47 | }) 48 | }, 49 | ({ container }) => { 50 | const mediaRule2 = postcss.atRule({ 51 | name: "media", 52 | params: "(min-width: 1040px)", 53 | }) 54 | mediaRule2.append(container.nodes) 55 | container.push(mediaRule2) 56 | mediaRule2.walkRules(rule => { 57 | rule.selector = `.collapsed .mdx\\:${rule.selector.slice(1)}` 58 | }) 59 | }, 60 | ]) 61 | }, 62 | function ({ matchUtilities, matchComponents, matchVariant, theme, e }) { 63 | matchUtilities({ 64 | tab(value) { 65 | return { 66 | tabSize: value, 67 | } 68 | }, 69 | }) 70 | matchComponents( 71 | { 72 | test(value) { 73 | return { 74 | "&.test": { 75 | backgroundColor: value, 76 | }, 77 | } 78 | }, 79 | }, 80 | { values: theme("colors.cyan") }, 81 | ) 82 | matchVariant({ 83 | tab(value) { 84 | if (value == null) return "& > *" 85 | return `&.${e(value ?? "")} > *` 86 | }, 87 | }) 88 | matchVariant({ 89 | screen(value) { 90 | return `@media (min-width: ${value ?? "0px"})` 91 | }, 92 | }) 93 | }, 94 | ], 95 | } as Tailwind.ConfigJS 96 | -------------------------------------------------------------------------------- /demo/theme.ts: -------------------------------------------------------------------------------- 1 | import tw, { theme } from "twin.macro" 2 | theme`colors.foo-5 / 0.1` 3 | theme`colors.foo-5/10 /10%` 4 | theme`colors.foo-5/10/ 0.1` 5 | theme`colors.foo-5/10 0.2` 6 | theme`colors.foo-5/10 10%` 7 | tw`accent-[theme(colors.foo-5 / 0.1)]` 8 | tw`accent-[theme(colors.foo-5/10 /10%)]` 9 | tw`accent-[theme(colors.foo-5/10 /10%)]` 10 | tw`accent-[theme(colors.foo-5/10 0.2)]` 11 | tw`accent-[theme(colors.foo-5/10 10%)]` 12 | theme`width.1/2` 13 | tw` 14 | // bug from tailwindcss 15 | accent-[calc(theme(width.1/2))] 16 | ` 17 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "node", 6 | "lib": ["ESNext"], 7 | "types": ["tailwind-types", "@types/node"], 8 | "noImplicitAny": false, 9 | "strict": true, 10 | "allowJs": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "removeComments": true, 16 | "noEmit": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/utopia.ts: -------------------------------------------------------------------------------- 1 | // https://utopia.fyi/type/calculator/?c=480,14,1.125,1300,16,1.125,10,3,&s=0.75%7C0.5%7C0.25,1.5%7C2%7C3%7C4%7C6,s-l 2 | 3 | export default function utopia(config: Array<[string, number, number, number]>, rootFontSize = 16): Tailwind.Plugin { 4 | const fontSize: Record = {} 5 | function variableName(minValue: number, maxValue: number) { 6 | return `--step-${minValue}〰️${maxValue}`.replace(/\./g, "_") 7 | } 8 | for (const [key, minValue, maxValue, lineHeight] of config) { 9 | fontSize[key] = [`var(${variableName(minValue, maxValue)})`, { lineHeight }] 10 | } 11 | const minWidth = 480 12 | const maxWidth = 1300 13 | return { 14 | config: { 15 | theme: { 16 | extend: { 17 | fontSize, 18 | }, 19 | }, 20 | }, 21 | handler({ addBase }) { 22 | config.forEach(([, minValue, maxValue]) => { 23 | generateVariable(minValue, maxValue) 24 | }) 25 | return 26 | function generateVariable(minValue: number, maxValue: number) { 27 | const style = {} 28 | style[variableName(minValue, maxValue)] = `clamp(${getStep(minValue, maxValue)})` 29 | addBase({ ":root": style }) 30 | function getStep(minFontSize: number, maxFontSize: number) { 31 | const m = minFontSize / rootFontSize 32 | const n = maxFontSize / rootFontSize 33 | const a = (maxFontSize - minFontSize) / (maxWidth - minWidth) 34 | const c = minFontSize - minWidth * a 35 | return `${m}rem, ${c / rootFontSize}rem + ${100 * a}vw, ${n}rem` 36 | } 37 | } 38 | }, 39 | } 40 | } 41 | 42 | export function fontSize( 43 | [mobileBaseSize, desktopBaseSize]: [number, number] = [14, 16], 44 | ): Array<[string, number, number, number]> { 45 | const ret: Array<[number, number, number]> = [[mobileBaseSize, desktopBaseSize, 1.5]] 46 | for (let i = 1; i <= 5; i++) { 47 | let [a, b] = ret[2 * (i - 1)] 48 | a += 2 ** i 49 | b += 2 ** i 50 | ret.push([a, b, lineHeight(b, desktopBaseSize - 2, desktopBaseSize)]) 51 | a += 2 ** i 52 | b += 2 ** i 53 | ret.push([a, b, lineHeight(b, desktopBaseSize - 2, desktopBaseSize)]) 54 | } 55 | 56 | ret.unshift([mobileBaseSize - 2, desktopBaseSize - 2, 1.3], [mobileBaseSize - 1, desktopBaseSize - 1, 1.4]) 57 | 58 | const labels = ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl", "7xl", "8xl", "9xl"] 59 | 60 | return labels.map((label, i) => [label, ...ret[i]]) 61 | } 62 | 63 | function lineHeight(fontSize: number, desktopMinSize: number, desktopBaseSize: number) { 64 | const h = 1.5 - (0.03 * (fontSize - desktopMinSize)) / (desktopBaseSize - desktopMinSize) 65 | if (h < 1) return 1 66 | return h 67 | } 68 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | - Redirect to [lightyen/twin.examples](https://github.com/lightyen/twin.examples) 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.(t|j)sx?$": "@swc/jest", 4 | }, 5 | modulePathIgnorePatterns: ["examples"], 6 | moduleNameMapper: { 7 | "^~/(.*)": "/src/$1", 8 | "vscode-css-languageservice/lib/esm/(.*)": "vscode-css-languageservice/lib/umd/$1", 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightyen/vscode-tailwindcss-twin/344ee67f77a640ffb7458b1ebab895abcff65b79/media/icon.png -------------------------------------------------------------------------------- /media/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {/* */} 11 | 12 | -------------------------------------------------------------------------------- /media/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightyen/vscode-tailwindcss-twin/344ee67f77a640ffb7458b1ebab895abcff65b79/media/icon.xcf -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext.settings.enabled": "Enable tailwind twin extension.", 3 | "ext.settings.colorDecorators": "Controls whether the extension should render the inline color decorators.\n- **inherit**: Inherit from `editor.colorDecorators`.\n- **on**: Enabled.\n- **off**: Disabled.", 4 | "ext.settings.references": "Controls whether the extension should show references when completion/hover.", 5 | "ext.settings.diagnostics": "Diagnostics", 6 | "ext.settings.diagnostics.enabled": "Controls whether diagnostics enabled.", 7 | "ext.settings.jsxPropImportChecking": "jsx prop import checking(set it to false if using with babel-plugin-twin).", 8 | "ext.settings.logging.level": "The logging level the extension logs at, defaults to 'info'", 9 | "ext.debug-outout.version": "version" 10 | } 11 | -------------------------------------------------------------------------------- /package.nls.zh-tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext.settings.enabled": "啟用", 3 | "ext.settings.colorDecorators": "啟用顏色提示高亮。", 4 | "ext.settings.references": "啟用文件資料參考提示。", 5 | "ext.settings.diagnostics": "診斷重複屬性或者空值。", 6 | "ext.settings.diagnostics.enabled": "啟用診斷。", 7 | "ext.settings.jsxPropImportChecking": "檢查 twin 庫是否已導入。(babel-plugin-twin 的情境請設定為 false)", 8 | "ext.settings.logging.level": "Twin 擴充套件的日誌層級,預設為 'info'", 9 | "ext.debug-outout.version": "版本" 10 | } 11 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightyen/vscode-tailwindcss-twin/344ee67f77a640ffb7458b1ebab895abcff65b79/preview.gif -------------------------------------------------------------------------------- /src/colorPresentations.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | function toTwoDigitHex(n: number): string { 4 | const r = n.toString(16) 5 | return r.length !== 2 ? "0" + r : r 6 | } 7 | 8 | function hslFromColor(rgba: vscode.Color) { 9 | const r = rgba.red 10 | const g = rgba.green 11 | const b = rgba.blue 12 | const a = rgba.alpha 13 | 14 | const max = Math.max(r, g, b) 15 | const min = Math.min(r, g, b) 16 | let h = 0 17 | let s = 0 18 | const l = (min + max) / 2 19 | const chroma = max - min 20 | 21 | if (chroma > 0) { 22 | s = Math.min(l <= 0.5 ? chroma / (2 * l) : chroma / (2 - 2 * l), 1) 23 | 24 | switch (max) { 25 | case r: 26 | h = (g - b) / chroma + (g < b ? 6 : 0) 27 | break 28 | case g: 29 | h = (b - r) / chroma + 2 30 | break 31 | case b: 32 | h = (r - g) / chroma + 4 33 | break 34 | } 35 | 36 | h *= 60 37 | h = Math.round(h) 38 | } 39 | return { h, s, l, a } 40 | } 41 | 42 | export const provideColorPresentations: vscode.DocumentColorProvider["provideColorPresentations"] = ( 43 | color, 44 | { document, range }, 45 | ) => { 46 | const result: vscode.ColorPresentation[] = [] 47 | const level4 = document.getText(range).indexOf(",") === -1 48 | const red256 = Math.round(color.red * 255), 49 | green256 = Math.round(color.green * 255), 50 | blue256 = Math.round(color.blue * 255) 51 | 52 | let label: string 53 | if (color.alpha === 1) { 54 | label = `rgb(${[red256, green256, blue256].join(level4 ? " " : ", ")})` 55 | } else if (level4) { 56 | label = `rgba(${[red256, green256, blue256].join(" ")} / ${color.alpha})` 57 | } else { 58 | label = `rgba(${red256}, ${green256}, ${blue256}, ${color.alpha})` 59 | } 60 | result.push({ label: label, textEdit: vscode.TextEdit.replace(range, label) }) 61 | 62 | if (color.alpha === 1) { 63 | label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}` 64 | } else { 65 | label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}${toTwoDigitHex( 66 | Math.round(color.alpha * 255), 67 | )}` 68 | } 69 | result.push({ label: label, textEdit: vscode.TextEdit.replace(range, label) }) 70 | 71 | const hsl = hslFromColor(color) 72 | if (hsl.a === 1) { 73 | label = `hsl(${[hsl.h, Math.round(hsl.s * 100) + "%", Math.round(hsl.l * 100) + "%"].join( 74 | level4 ? " " : ", ", 75 | )})` 76 | } else if (level4) { 77 | label = `hsla(${hsl.h} ${Math.round(hsl.s * 100)}% ${Math.round(hsl.l * 100)}% / ${hsl.a})` 78 | } else { 79 | label = `hsla(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%, ${hsl.a})` 80 | } 81 | result.push({ label: label, textEdit: vscode.TextEdit.replace(range, label) }) 82 | 83 | return result 84 | } 85 | -------------------------------------------------------------------------------- /src/common/culori.ts: -------------------------------------------------------------------------------- 1 | import { RgbSpace } from "culori" 2 | 3 | function clone(rgb: RgbSpace): RgbSpace { 4 | return { ...rgb } 5 | } 6 | 7 | export function ensureContrastRatio(rgb: RgbSpace, base: RgbSpace, ratio: number): RgbSpace | undefined { 8 | const rgbL = relativeLuminance(rgb) 9 | const baseL = relativeLuminance(base) 10 | if (contrastRatio(rgbL, baseL) < ratio) { 11 | const increased = clone(rgb) 12 | increaseLuminance(increased, base, ratio) 13 | if (contrastRatio(relativeLuminance(increased), baseL) >= ratio) { 14 | return increased 15 | } 16 | const reduced = clone(rgb) 17 | reduceLuminance(reduced, base, ratio) 18 | return reduced 19 | } 20 | return undefined 21 | } 22 | 23 | export function relativeLuminance({ r, g, b }: RgbSpace) { 24 | r = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4) 25 | g = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4) 26 | b = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4) 27 | return 0.2126 * r + 0.7152 * g + 0.0722 * b 28 | } 29 | function contrastRatio(l1: number, l2: number) { 30 | if (l1 < l2) return (l2 + 0.05) / (l1 + 0.05) 31 | return (l1 + 0.05) / (l2 + 0.05) 32 | } 33 | 34 | function reduceLuminance(rgb: RgbSpace, base: RgbSpace, ratio: number) { 35 | const baseL = relativeLuminance(base) 36 | const p = 0.1 37 | let r = contrastRatio(relativeLuminance(rgb), baseL) 38 | const e = 10e-4 39 | while (r < ratio && (rgb.r > e || rgb.g > e || rgb.b > e)) { 40 | rgb.r = (1 - p) * rgb.r 41 | rgb.g = (1 - p) * rgb.g 42 | rgb.b = (1 - p) * rgb.b 43 | r = contrastRatio(relativeLuminance(rgb), baseL) 44 | } 45 | } 46 | 47 | function increaseLuminance(rgb: RgbSpace, base: RgbSpace, ratio: number) { 48 | const baseL = relativeLuminance(base) 49 | const p = 0.1 50 | let r = contrastRatio(relativeLuminance(rgb), baseL) 51 | const e = 1 - 10e-4 52 | while (r < ratio && (rgb.r < e || rgb.g < e || rgb.b < e)) { 53 | rgb.r = rgb.r + (1 - rgb.r) * p 54 | rgb.g = rgb.g + (1 - rgb.g) * p 55 | rgb.b = rgb.b + (1 - rgb.b) * p 56 | r = contrastRatio(relativeLuminance(rgb), baseL) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/common/extractors/default.ts: -------------------------------------------------------------------------------- 1 | import rawExtrator from "./raw" 2 | import typescriptExtractor from "./typescript" 3 | export const defaultExtractors = [typescriptExtractor, rawExtrator] 4 | -------------------------------------------------------------------------------- /src/common/extractors/raw.ts: -------------------------------------------------------------------------------- 1 | import type { Extractor } from "./types" 2 | 3 | const rawExtrator: Extractor = { 4 | acceptLanguage(languageId) { 5 | return languageId === "twin" 6 | }, 7 | findAll(languageId, code, jsxPropImportChecking) { 8 | return [ 9 | { 10 | start: 0, 11 | end: code.length, 12 | value: code, 13 | kind: "tw", 14 | }, 15 | ] 16 | }, 17 | find(languageId, code, position, hover, jsxPropImportChecking) { 18 | return { 19 | start: 0, 20 | end: code.length, 21 | value: code, 22 | kind: "tw", 23 | } 24 | }, 25 | } 26 | export default rawExtrator 27 | -------------------------------------------------------------------------------- /src/common/extractors/types.ts: -------------------------------------------------------------------------------- 1 | import type typescript from "typescript" 2 | import type vscode from "vscode" 3 | import type { URI } from "vscode-uri" 4 | import type { Logger } from "../logger" 5 | 6 | type LanguageId = "javascript" | "javascriptreact" | "typescript" | "typescriptreact" | "twin" | string 7 | 8 | export interface TextDocument { 9 | offsetAt(position: Position): number 10 | getText(): string 11 | positionAt(offset: number): Position 12 | languageId: LanguageId 13 | uri: URI 14 | } 15 | 16 | export type ExtractedTokenKind = "tw" | "theme" | "screen" | "cs" 17 | 18 | export interface Token { 19 | start: number 20 | end: number 21 | value: string 22 | } 23 | 24 | export interface ExtractedToken extends Token { 25 | kind: ExtractedTokenKind 26 | } 27 | 28 | export interface Extractor { 29 | acceptLanguage(languageId: LanguageId): boolean 30 | find( 31 | languageId: LanguageId, 32 | code: string, 33 | position: number, 34 | includeEnd: boolean, 35 | jsxPropImportChecking: boolean, 36 | context: { console: Logger; typescript: typeof typescript; typescriptExtractor: Extractor }, 37 | ): ExtractedToken | undefined 38 | findAll( 39 | languageId: LanguageId, 40 | code: string, 41 | jsxPropImportChecking: boolean, 42 | context: { console: Logger; typescript: typeof typescript; typescriptExtractor: Extractor }, 43 | ): ExtractedToken[] 44 | } 45 | -------------------------------------------------------------------------------- /src/common/get_set.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export function dlv(cur: any, paths: string[]): any { 3 | if (cur == undefined) { 4 | return undefined 5 | } 6 | for (let i = 0; i < paths.length; ++i) { 7 | if (cur[paths[i]] == undefined) { 8 | return undefined 9 | } else { 10 | cur = cur[paths[i]] 11 | } 12 | } 13 | return cur 14 | } 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | export function dset(cur: any, paths: Array, value: unknown) { 18 | if (cur == undefined || paths.length === 0) { 19 | return 20 | } 21 | for (let i = 0; i < paths.length - 1; ++i) { 22 | const key = paths[i] 23 | if (cur[key] == undefined) { 24 | // if next key is digit number 25 | cur[key] = +paths[i + 1] > -1 ? new Array(0) : {} 26 | } 27 | cur = cur[key] 28 | } 29 | const last = paths[paths.length - 1] 30 | cur[last] = value 31 | } 32 | -------------------------------------------------------------------------------- /src/common/idebounce.ts: -------------------------------------------------------------------------------- 1 | const m: Record = {} 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | export default function idebounce(key: string, callback: (...args: any[]) => T, ...args: any[]) { 5 | const timeout = 16 6 | const h = m[key] 7 | if (h) { 8 | clearTimeout(h) 9 | } 10 | return new Promise(resolve => { 11 | m[key] = setTimeout(() => { 12 | resolve(callback(...args)) 13 | m[key] = null 14 | }, timeout) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto" 2 | 3 | export function md5(value: string) { 4 | return crypto.createHash("md5").update(value, "utf-8").digest("hex") 5 | } 6 | 7 | export function escapeRegexp(expression: string) { 8 | return expression.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&") 9 | } 10 | 11 | /** accept strings: `1/4` */ 12 | export function calcFraction(value: string): number { 13 | const i = value.indexOf("/") 14 | if (i === -1) return NaN 15 | const a = Number(value.slice(0, i)) 16 | const b = Number(value.slice(i + 1)) 17 | if (Number.isNaN(a) || Number.isNaN(b)) return NaN 18 | return a / b 19 | } 20 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import { formatWithOptions } from "util" 2 | import type { OutputChannel } from "vscode" 3 | 4 | enum LogLevel { 5 | None, 6 | Error, 7 | Warning, 8 | Info, 9 | Debug, 10 | Trace, 11 | } 12 | 13 | export type LogLevelString = "none" | "error" | "warning" | "info" | "debug" | "trace" 14 | 15 | function level2LevelString(logLevel: LogLevel | LogLevelString) { 16 | switch (logLevel) { 17 | case LogLevel.None: 18 | return "none" 19 | case LogLevel.Error: 20 | return "error" 21 | case LogLevel.Warning: 22 | return "warning" 23 | case LogLevel.Info: 24 | return "info" 25 | case LogLevel.Debug: 26 | return "debug" 27 | case LogLevel.Trace: 28 | return "trace" 29 | default: 30 | return logLevel 31 | } 32 | } 33 | 34 | function levelString2LogLevel(logLevel: LogLevel | LogLevelString) { 35 | switch (logLevel) { 36 | case "error": 37 | return LogLevel.Error 38 | case "warning": 39 | return LogLevel.Warning 40 | case "info": 41 | return LogLevel.Info 42 | case "debug": 43 | return LogLevel.Debug 44 | case "trace": 45 | return LogLevel.Trace 46 | default: 47 | return LogLevel.None 48 | } 49 | } 50 | 51 | interface Options { 52 | logLevel: LogLevel | LogLevelString 53 | colors?: boolean 54 | outputChannel?: OutputChannel 55 | } 56 | 57 | export interface Logger { 58 | error(...args: unknown[]): void 59 | warn(...args: unknown[]): void 60 | info(...args: unknown[]): void 61 | debug(...args: unknown[]): void 62 | trace(...args: unknown[]): void 63 | /** @deprecated */ 64 | log(...args: unknown[]): void 65 | set level(level: LogLevelString) 66 | get level(): LogLevelString 67 | set outputChannel(ch: OutputChannel) 68 | set outputMode(mode: "debugConsole" | "outputChannel" | "all") 69 | } 70 | 71 | export function createLogger({ 72 | logLevel = LogLevel.Info, 73 | colors = true, 74 | outputChannel: _outputChannel, 75 | }: Partial = {}): Logger { 76 | let _mode: "debugConsole" | "outputChannel" | "all" = "outputChannel" 77 | if (typeof logLevel === "string") { 78 | logLevel = levelString2LogLevel(logLevel) 79 | } 80 | 81 | return { 82 | error: (...args: unknown[]) => log(LogLevel.Error, args), 83 | warn: (...args: unknown[]) => log(LogLevel.Warning, args), 84 | info: (...args: unknown[]) => log(LogLevel.Info, args), 85 | debug: (...args: unknown[]) => log(LogLevel.Debug, args), 86 | trace: (...args: unknown[]) => log(LogLevel.Trace, args), 87 | log: (...args: unknown[]) => log(LogLevel.Info, args), 88 | set outputChannel(ch: OutputChannel) { 89 | _outputChannel = ch 90 | }, 91 | set outputMode(mode: "debugConsole" | "outputChannel" | "all") { 92 | _mode = mode 93 | }, 94 | set level(level: LogLevelString) { 95 | logLevel = levelString2LogLevel(level) 96 | }, 97 | get level() { 98 | return level2LevelString(logLevel) 99 | }, 100 | } as const 101 | 102 | function log(level: LogLevel, args: unknown[]) { 103 | if (logLevel < level) { 104 | return 105 | } 106 | const datetime = `[${new Date().toLocaleString()}]` 107 | const lv = `[${level2LevelString(level)}]` 108 | if (_mode === "outputChannel" || _mode === "all") { 109 | _outputChannel?.appendLine(formatWithOptions({ colors: false, depth: 3 }, datetime, lv, ...args)) 110 | if (_mode === "outputChannel") return 111 | } 112 | if (colors && typeof args[0] === "string") { 113 | args.unshift(lv) 114 | } 115 | switch (level) { 116 | case LogLevel.Error: 117 | console.error(...args) 118 | break 119 | case LogLevel.Warning: 120 | console.warn(...args) 121 | break 122 | case LogLevel.Info: 123 | console.info(...args) 124 | break 125 | case LogLevel.Debug: 126 | console.debug(...args) 127 | break 128 | case LogLevel.Trace: 129 | console.debug(...args) 130 | break 131 | } 132 | } 133 | } 134 | 135 | export const defaultLogger = createLogger() 136 | -------------------------------------------------------------------------------- /src/common/markdown.ts: -------------------------------------------------------------------------------- 1 | export enum CodeKind { 2 | CSS = "css", 3 | SCSS = "scss", 4 | } 5 | 6 | export function createFencedCodeBlock(code: string, kind: CodeKind, beginNewLine = false) { 7 | switch (kind) { 8 | case CodeKind.CSS: 9 | return "```css" + (beginNewLine ? "\n\n" : "\n") + code + "\n```" 10 | case CodeKind.SCSS: 11 | return "```scss" + (beginNewLine ? "\n\n" : "\n") + code + "\n```" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/common/parser/__tests__/__snapshots__/hover.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`parser util hover 1`] = ` 4 | Object { 5 | "important": true, 6 | "target": Object { 7 | "important": false, 8 | "range": Array [ 9 | 106, 10 | 118, 11 | ], 12 | "type": "ClassName", 13 | "value": "text-red-700", 14 | }, 15 | "value": "text-red-700", 16 | "variants": Array [ 17 | " 18 | .group:hover, 19 | // .group:focus] 20 | ", 21 | ], 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/common/parser/__tests__/hover.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeType } from ".." 2 | import { hover } from "../hover" 3 | 4 | it("parser util hover", () => { 5 | const code = `bg-gray-500 padding[ 3rem ]! 6 | hover:(border-4 border-blue-500) 7 | [ 8 | .group:hover, 9 | // .group:focus] 10 | ]:!(text-red-700)// bg-primary 11 | /* text-error () 12 | */() 13 | custom/30 text-yellow-600/80 bg-gray-500/[.3] 14 | bg-[url(http://example)] 15 | text-[rgba(3 3 3 / .8)]/[.2] sm:hover: ( q-test` 16 | 17 | expect( 18 | hover({ 19 | text: code, 20 | position: 32, 21 | }), 22 | ).toEqual({ 23 | target: { 24 | type: NodeType.SimpleVariant, 25 | range: [30, 36], 26 | id: { 27 | type: NodeType.Identifier, 28 | value: "hover", 29 | range: [30, 35], 30 | }, 31 | }, 32 | important: false, 33 | variants: [], 34 | value: "hover", 35 | }) 36 | 37 | expect( 38 | hover({ 39 | text: code, 40 | position: 117, 41 | }), 42 | ).toMatchSnapshot() 43 | }) 44 | -------------------------------------------------------------------------------- /src/common/parser/__tests__/spread.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeType } from ".." 2 | import { spread } from "../spread" 3 | 4 | it("parser util spread", () => { 5 | const result = spread(`bg-gray-500 padding[ 3rem ]! 6 | hover:(border-4 border-blue-500) 7 | [ 8 | .group:hover, 9 | // .group:focus] 10 | ]:!(text-red-700)// bg-primary 11 | /* text-error () 12 | */() 13 | custom/30 text-yellow-600/80 bg-gray-500/[.3] 14 | bg-[url(http://example)] 15 | text-[rgba(3 3 3 / .8)]/[.2] sm:hover: ( q-test`) 16 | expect(result.items.map(i => i.target.type)).toEqual([ 17 | NodeType.ClassName, 18 | NodeType.ShortCss, 19 | NodeType.ClassName, 20 | NodeType.ClassName, 21 | NodeType.ClassName, 22 | NodeType.ClassName, 23 | NodeType.ClassName, 24 | NodeType.ArbitraryClassname, 25 | NodeType.ArbitraryClassname, 26 | NodeType.ArbitraryClassname, 27 | ]) 28 | 29 | expect(result).toMatchSnapshot() 30 | }) 31 | 32 | it("spread all types", () => { 33 | const text = ` 34 | class-value 35 | (class-value) 36 | hover:class-value 37 | hover:(class-value) 38 | []:class-value 39 | hover:[]:class-value 40 | any-[]:class-value 41 | hover:any-[]:class-value 42 | class-[value] 43 | class-value/opacity 44 | class-[value]/[opacity] 45 | class-[value]/opacity 46 | class-value/[opacity] 47 | [prop: value] 48 | prop[value] 49 | x 50 | 51 | // important prefix 52 | !class-value 53 | !(class-value) 54 | hover:!class-value 55 | hover:!(class-value) 56 | []:!class-value 57 | hover:[]:!class-value 58 | any-[]:!class-value 59 | hover:any-[]:!class-value 60 | !class-[value] 61 | !class-value/opacity 62 | !class-[value]/[opacity] 63 | !class-[value]/opacity 64 | !class-value/[opacity] 65 | ![prop: value] 66 | !prop[value] 67 | 68 | // important after 69 | class-value! 70 | (class-value)! 71 | hover:class-value! 72 | hover:(class-value)! 73 | []:class-value! 74 | hover:[]:class-value! 75 | any-[]:class-value! 76 | hover:any-[]:class-value! 77 | class-[value]! 78 | class-value/opacity! 79 | class-[value]/[opacity]! 80 | class-[value]/opacity! 81 | class-value/[opacity]! 82 | [prop: value]! 83 | prop[value]! 84 | ` 85 | expect(spread(text).items.map(i => i.target.type)).toEqual([ 86 | NodeType.ClassName, 87 | NodeType.ClassName, 88 | NodeType.ClassName, 89 | NodeType.ClassName, 90 | NodeType.ClassName, 91 | NodeType.ClassName, 92 | NodeType.ClassName, 93 | NodeType.ClassName, 94 | NodeType.ArbitraryClassname, 95 | NodeType.ClassName, 96 | NodeType.ArbitraryClassname, 97 | NodeType.ArbitraryClassname, 98 | NodeType.ArbitraryClassname, 99 | NodeType.ArbitraryProperty, 100 | NodeType.ShortCss, 101 | NodeType.ClassName, 102 | NodeType.ClassName, 103 | NodeType.ClassName, 104 | NodeType.ClassName, 105 | NodeType.ClassName, 106 | NodeType.ClassName, 107 | NodeType.ClassName, 108 | NodeType.ClassName, 109 | NodeType.ClassName, 110 | NodeType.ArbitraryClassname, 111 | NodeType.ClassName, 112 | NodeType.ArbitraryClassname, 113 | NodeType.ArbitraryClassname, 114 | NodeType.ArbitraryClassname, 115 | NodeType.ArbitraryProperty, 116 | NodeType.ShortCss, 117 | NodeType.ClassName, 118 | NodeType.ClassName, 119 | NodeType.ClassName, 120 | NodeType.ClassName, 121 | NodeType.ClassName, 122 | NodeType.ClassName, 123 | NodeType.ClassName, 124 | NodeType.ClassName, 125 | NodeType.ArbitraryClassname, 126 | NodeType.ClassName, 127 | NodeType.ArbitraryClassname, 128 | NodeType.ArbitraryClassname, 129 | NodeType.ArbitraryClassname, 130 | NodeType.ArbitraryProperty, 131 | NodeType.ShortCss, 132 | ]) 133 | 134 | expect(spread(text).items.map(i => i.important)).toEqual([ 135 | false, 136 | false, 137 | false, 138 | false, 139 | false, 140 | false, 141 | false, 142 | false, 143 | false, 144 | false, 145 | false, 146 | false, 147 | false, 148 | false, 149 | false, 150 | false, 151 | true, 152 | true, 153 | true, 154 | true, 155 | true, 156 | true, 157 | true, 158 | true, 159 | true, 160 | true, 161 | true, 162 | true, 163 | true, 164 | true, 165 | true, 166 | true, 167 | true, 168 | true, 169 | true, 170 | true, 171 | true, 172 | true, 173 | true, 174 | true, 175 | true, 176 | true, 177 | true, 178 | true, 179 | true, 180 | true, 181 | ]) 182 | 183 | expect(spread(text).items.map(i => i.target.important)).toEqual([ 184 | false, 185 | false, 186 | false, 187 | false, 188 | false, 189 | false, 190 | false, 191 | false, 192 | false, 193 | false, 194 | false, 195 | false, 196 | false, 197 | false, 198 | false, 199 | false, 200 | true, 201 | false, 202 | true, 203 | false, 204 | true, 205 | true, 206 | true, 207 | true, 208 | true, 209 | true, 210 | true, 211 | true, 212 | true, 213 | true, 214 | true, 215 | true, 216 | false, 217 | true, 218 | false, 219 | true, 220 | true, 221 | true, 222 | true, 223 | true, 224 | true, 225 | true, 226 | true, 227 | true, 228 | true, 229 | true, 230 | ]) 231 | }) 232 | 233 | it("parser arbitrary variant", () => { 234 | const result = spread(`tab-[]:black`) 235 | expect(result).toMatchSnapshot() 236 | }) 237 | -------------------------------------------------------------------------------- /src/common/parser/__tests__/theme.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeType, ThemePathNode } from "../nodes" 2 | import { tryOpacityValue } from "../theme" 3 | 4 | it("tryOpacityValue 1", () => { 5 | // "colors.foo-5 / 0.1" 6 | // -> "colors.foo-5", "0.1" 7 | const pathRaw: ThemePathNode[] = [ 8 | { 9 | type: NodeType.ThemePath, 10 | closed: true, 11 | value: "colors", 12 | range: [0, 6], 13 | toString() { 14 | return "colors" 15 | }, 16 | }, 17 | { 18 | type: NodeType.ThemePath, 19 | closed: true, 20 | value: "foo-5", 21 | range: [6, 12], 22 | toString() { 23 | return ".foo-5" 24 | }, 25 | }, 26 | { 27 | type: NodeType.ThemePath, 28 | closed: true, 29 | value: "/", 30 | range: [13, 14], 31 | toString() { 32 | return "/" 33 | }, 34 | }, 35 | { 36 | type: NodeType.ThemePath, 37 | closed: true, 38 | value: "0", 39 | range: [15, 16], 40 | toString() { 41 | return "0" 42 | }, 43 | }, 44 | { 45 | type: NodeType.ThemePath, 46 | closed: true, 47 | value: "1", 48 | range: [16, 18], 49 | toString() { 50 | return ".1" 51 | }, 52 | }, 53 | ] 54 | 55 | const { opacityValue, path } = tryOpacityValue(pathRaw) 56 | expect(opacityValue).toEqual("0.1") 57 | expect(path.map(p => p.value)).toEqual(["colors", "foo-5"]) 58 | }) 59 | 60 | it("tryOpacityValue 2", () => { 61 | // "colors.foo-5/10 /10%" 62 | // -> "colors.foo-5/10", "10%" 63 | const pathRaw: ThemePathNode[] = [ 64 | { 65 | type: NodeType.ThemePath, 66 | closed: true, 67 | value: "colors", 68 | range: [0, 6], 69 | toString() { 70 | return "colors" 71 | }, 72 | }, 73 | { 74 | type: NodeType.ThemePath, 75 | closed: true, 76 | value: "foo-5/10", 77 | range: [6, 15], 78 | toString() { 79 | return ".foo-5/10" 80 | }, 81 | }, 82 | { 83 | type: NodeType.ThemePath, 84 | closed: true, 85 | value: "/10%", 86 | range: [16, 20], 87 | toString() { 88 | return "/10%" 89 | }, 90 | }, 91 | ] 92 | 93 | const { opacityValue, path } = tryOpacityValue(pathRaw) 94 | expect(opacityValue).toEqual("10%") 95 | expect(path.map(p => p.value)).toEqual(["colors", "foo-5/10"]) 96 | }) 97 | 98 | it("tryOpacityValue 3", () => { 99 | // "colors.foo-5/10/0.1" 100 | // -> "colors.foo-5/10", "0.1" 101 | const pathRaw: ThemePathNode[] = [ 102 | { 103 | type: NodeType.ThemePath, 104 | closed: true, 105 | value: "colors", 106 | range: [0, 6], 107 | toString() { 108 | return "colors" 109 | }, 110 | }, 111 | { 112 | type: NodeType.ThemePath, 113 | closed: true, 114 | value: "foo-5/10/", 115 | range: [6, 16], 116 | toString() { 117 | return ".foo-5/10/" 118 | }, 119 | }, 120 | { 121 | type: NodeType.ThemePath, 122 | closed: true, 123 | value: "0", 124 | range: [16, 17], 125 | toString() { 126 | return "0" 127 | }, 128 | }, 129 | { 130 | type: NodeType.ThemePath, 131 | closed: true, 132 | value: "1", 133 | range: [17, 19], 134 | toString() { 135 | return ".1" 136 | }, 137 | }, 138 | ] 139 | 140 | const { opacityValue, path } = tryOpacityValue(pathRaw) 141 | expect(opacityValue).toEqual("0.1") 142 | expect(path.map(p => p.value)).toEqual(["colors", "foo-5/10"]) 143 | }) 144 | 145 | it("tryOpacityValue 4", () => { 146 | // "colors.foo-5/10/ 0.1" 147 | // -> "colors.foo-5/10", "0.1" 148 | const pathRaw: ThemePathNode[] = [ 149 | { 150 | type: NodeType.ThemePath, 151 | closed: true, 152 | value: "colors", 153 | range: [0, 6], 154 | toString() { 155 | return "colors" 156 | }, 157 | }, 158 | { 159 | type: NodeType.ThemePath, 160 | closed: true, 161 | value: "foo-5/10/", 162 | range: [6, 16], 163 | toString() { 164 | return ".foo-5/10/" 165 | }, 166 | }, 167 | { 168 | type: NodeType.ThemePath, 169 | closed: true, 170 | value: "0", 171 | range: [17, 18], 172 | toString() { 173 | return "0" 174 | }, 175 | }, 176 | { 177 | type: NodeType.ThemePath, 178 | closed: true, 179 | value: "1", 180 | range: [18, 20], 181 | toString() { 182 | return ".1" 183 | }, 184 | }, 185 | ] 186 | 187 | const { opacityValue, path } = tryOpacityValue(pathRaw) 188 | expect(opacityValue).toEqual("0.1") 189 | expect(path.map(p => p.value)).toEqual(["colors", "foo-5/10"]) 190 | }) 191 | -------------------------------------------------------------------------------- /src/common/parser/hover.ts: -------------------------------------------------------------------------------- 1 | import * as nodes from "./nodes" 2 | import * as parser from "./parse_regexp" 3 | import { getVariant } from "./util" 4 | 5 | interface HoverResult { 6 | target: 7 | | nodes.SimpleVariant 8 | | nodes.ArbitrarySelector 9 | | nodes.ArbitraryVariant 10 | | nodes.Classname 11 | | nodes.ArbitraryClassname 12 | | nodes.ArbitraryProperty 13 | | nodes.ShortCss 14 | value: string 15 | variants: string[] 16 | important: boolean 17 | } 18 | 19 | export function hover({ 20 | position, 21 | text, 22 | separator = ":", 23 | }: { 24 | position: number 25 | text: string 26 | separator?: string 27 | }): HoverResult | undefined { 28 | interface Context { 29 | important: boolean 30 | variants: string[] 31 | } 32 | 33 | const inRange = (node: nodes.Node) => position >= node.range[0] && position < node.range[1] 34 | 35 | const travel = (node: nodes.Node, ctx: Context): HoverResult | undefined => { 36 | const walk = (node: nodes.Node): HoverResult | undefined => { 37 | if (nodes.NodeType.VariantSpan === node.type) { 38 | const variants = ctx.variants.slice() 39 | if (inRange(node.variant)) { 40 | return { 41 | target: node.variant, 42 | value: getVariant(node.variant, separator).value, 43 | important: false, 44 | variants, 45 | } 46 | } 47 | if (!node.child) return undefined 48 | variants.push(getVariant(node.variant, separator).value) 49 | return travel(node.child, { ...ctx, variants }) 50 | } 51 | 52 | if (nodes.NodeType.Group === node.type) { 53 | return travel( 54 | { type: nodes.NodeType.Program, expressions: node.expressions, range: node.range }, 55 | { ...ctx, important: ctx.important || node.important }, 56 | ) 57 | } 58 | 59 | if (nodes.NodeType.ClassName === node.type) { 60 | return { 61 | target: node, 62 | value: text.slice(node.range[0], node.range[1]), 63 | important: ctx.important || node.important, 64 | variants: ctx.variants, 65 | } 66 | } 67 | 68 | if (nodes.NodeType.ShortCss === node.type) { 69 | return { 70 | target: node, 71 | value: text.slice(node.expr.range[0], node.expr.range[1]), 72 | important: ctx.important || node.important, 73 | variants: ctx.variants, 74 | } 75 | } 76 | 77 | if (nodes.NodeType.ArbitraryProperty === node.type) { 78 | return { 79 | target: node, 80 | value: text.slice(node.decl.range[0], node.decl.range[1]), 81 | important: ctx.important || node.important, 82 | variants: ctx.variants, 83 | } 84 | } 85 | 86 | if (nodes.NodeType.ArbitraryClassname === node.type) { 87 | return { 88 | target: node, 89 | value: text.slice(node.range[0], node.range[1]), 90 | important: ctx.important || node.important, 91 | variants: ctx.variants, 92 | } 93 | } 94 | 95 | return undefined 96 | } 97 | 98 | if (node == undefined) { 99 | return undefined 100 | } 101 | 102 | if (nodes.NodeType.Program === node.type) { 103 | const expr = node.expressions.find(inRange) 104 | if (expr) return walk(expr) 105 | return undefined 106 | } 107 | 108 | if (inRange(node)) { 109 | return walk(node) 110 | } 111 | 112 | return undefined 113 | } 114 | 115 | parser.setSeparator(separator) 116 | return travel(parser.parse(text, { breac: position }), { 117 | important: false, 118 | variants: [], 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /src/common/parser/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./hover" 2 | export * from "./nodes" 3 | export * from "./parse_regexp" 4 | export * from "./parse_theme" 5 | export * from "./spread" 6 | export * from "./suggest" 7 | export * from "./theme" 8 | export * from "./util" 9 | -------------------------------------------------------------------------------- /src/common/parser/nodes.ts: -------------------------------------------------------------------------------- 1 | // NOTE: 2 | // 3 | // classname: aa-bbb 4 | // arbitrary-classname: aa-[value] 5 | // arbitrary-property: [aaa: value] 6 | // simple-variant: aa-bbb: 7 | // arbitrary-variant: aa-[value]: 8 | // arbitrary-selector: [&:bbb, &:ccc]: 9 | // short-css: aaa[value] 10 | 11 | export enum NodeType { 12 | Program = "Program", 13 | Group = "Group", 14 | VariantSpan = "VariantSpan", 15 | ClassName = "ClassName", 16 | SimpleVariant = "SimpleVariant", 17 | ArbitraryClassname = "ArbitraryClassname", 18 | ArbitraryVariant = "ArbitraryVariant", 19 | ArbitraryProperty = "ArbitraryProperty", 20 | ArbitrarySelector = "ArbitrarySelector", 21 | CssSelector = "CssSelector", 22 | Identifier = "Identifier", 23 | ShortCss = "ShortCss", 24 | CssDeclaration = "CssDeclaration", 25 | CssExpression = "CssExpression", 26 | WithOpacity = "WithOpacity", 27 | EndOpacity = "EndOpacity", 28 | ThemeFunction = "ThemeFunction", 29 | ThemeValue = "ThemeValue", 30 | ThemePath = "ThemePath", 31 | } 32 | 33 | export type Range = [number, number] 34 | 35 | export interface NodeToken { 36 | range: Range 37 | } 38 | 39 | export interface NodeData { 40 | value: string 41 | } 42 | 43 | export type TokenString = NodeToken & NodeData 44 | 45 | export interface BaseNode extends NodeToken { 46 | type: NodeType 47 | } 48 | 49 | export interface Important { 50 | important: boolean 51 | } 52 | 53 | export interface Closed { 54 | closed: boolean 55 | } 56 | 57 | export interface Identifier extends BaseNode, NodeData { 58 | type: NodeType.Identifier 59 | } 60 | 61 | export interface SimpleVariant extends BaseNode { 62 | type: NodeType.SimpleVariant 63 | id: Identifier 64 | } 65 | 66 | export interface ArbitrarySelector extends BaseNode { 67 | type: NodeType.ArbitrarySelector 68 | selector: CssSelector 69 | } 70 | 71 | export interface ArbitraryVariant extends BaseNode { 72 | type: NodeType.ArbitraryVariant 73 | prefix: Identifier 74 | selector: CssSelector 75 | } 76 | 77 | export type Variant = SimpleVariant | ArbitrarySelector | ArbitraryVariant 78 | 79 | export interface CssSelector extends BaseNode, NodeData { 80 | type: NodeType.CssSelector 81 | } 82 | 83 | export interface Classname extends BaseNode, NodeData, Important { 84 | type: NodeType.ClassName 85 | } 86 | 87 | export interface CssExpression extends BaseNode, NodeData { 88 | type: NodeType.CssExpression 89 | } 90 | 91 | export interface WithOpacity extends BaseNode, Closed { 92 | type: NodeType.WithOpacity 93 | opacity: Identifier 94 | } 95 | 96 | export interface EndOpacity extends BaseNode, NodeData { 97 | type: NodeType.EndOpacity 98 | } 99 | 100 | export interface ArbitraryClassname extends BaseNode, Important, Closed { 101 | type: NodeType.ArbitraryClassname 102 | prefix: Identifier 103 | expr?: CssExpression 104 | e?: WithOpacity | EndOpacity 105 | } 106 | 107 | export interface CssDeclaration extends BaseNode, NodeData { 108 | type: NodeType.CssDeclaration 109 | } 110 | 111 | export interface ArbitraryProperty extends BaseNode, NodeData, Important, Closed { 112 | type: NodeType.ArbitraryProperty 113 | decl: CssDeclaration 114 | } 115 | 116 | export interface ShortCss extends BaseNode, Important, Closed { 117 | type: NodeType.ShortCss 118 | prefix: Identifier 119 | expr: CssExpression 120 | } 121 | 122 | export interface VariantSpan extends BaseNode { 123 | type: NodeType.VariantSpan 124 | variant: SimpleVariant | ArbitrarySelector | ArbitraryVariant 125 | child?: TwExpression 126 | } 127 | 128 | export type TwExpression = Classname | ArbitraryClassname | ArbitraryProperty | ShortCss | VariantSpan | Group 129 | 130 | export interface Group extends BaseNode, Important, Closed { 131 | type: NodeType.Group 132 | expressions: TwExpression[] 133 | } 134 | 135 | export interface Program extends BaseNode { 136 | type: NodeType.Program 137 | expressions: TwExpression[] 138 | } 139 | 140 | export type Node = 141 | | Program 142 | | TwExpression 143 | | SimpleVariant 144 | | ArbitrarySelector 145 | | ArbitraryVariant 146 | | CssSelector 147 | | CssExpression 148 | | WithOpacity 149 | | EndOpacity 150 | 151 | export type BracketNode = 152 | | Group 153 | | ArbitrarySelector 154 | | ArbitraryVariant 155 | | ArbitraryClassname 156 | | ArbitraryProperty 157 | | ShortCss 158 | | WithOpacity 159 | 160 | export interface ThemeFunctionNode extends BaseNode, Closed { 161 | type: NodeType.ThemeFunction 162 | value: ThemeValueNode 163 | valueRange: Range 164 | toString(): string 165 | } 166 | 167 | export interface ThemeValueNode extends BaseNode { 168 | type: NodeType.ThemeValue 169 | path: ThemePathNode[] 170 | toString(): string 171 | } 172 | 173 | export interface ThemePathNode extends BaseNode, NodeData, Closed { 174 | type: NodeType.ThemePath 175 | toString(): string 176 | } 177 | -------------------------------------------------------------------------------- /src/common/parser/parse_theme.ts: -------------------------------------------------------------------------------- 1 | import * as nodes from "./nodes" 2 | import { findRightBracket, isSpace } from "./util" 3 | 4 | const regexThemeFn = /\btheme\(/gs 5 | 6 | export function parse_theme({ text, start = 0, end = text.length }: { text: string; start?: number; end?: number }) { 7 | let ret: nodes.ThemeFunctionNode[] = [] 8 | while (start < end) { 9 | const { expr, lastIndex } = parse_theme_fn({ text, start, end }) 10 | if (expr) ret = ret.concat(expr) 11 | start = lastIndex 12 | if (!start) break 13 | } 14 | return ret 15 | } 16 | 17 | function parse_theme_fn({ text, start = 0, end = text.length }: { text: string; start?: number; end?: number }): { 18 | expr?: nodes.ThemeFunctionNode 19 | lastIndex: number 20 | } { 21 | regexThemeFn.lastIndex = start 22 | const match = regexThemeFn.exec(text) 23 | if (match == null) return { lastIndex: end } 24 | 25 | const rb = findRightBracket({ 26 | text, 27 | start: regexThemeFn.lastIndex - 1, 28 | end, 29 | }) 30 | if (rb == undefined) { 31 | const node: nodes.ThemeFunctionNode = { 32 | type: nodes.NodeType.ThemeFunction, 33 | closed: false, 34 | range: [match.index, end], 35 | valueRange: [regexThemeFn.lastIndex, end], 36 | value: parse_theme_val({ text, start: regexThemeFn.lastIndex, end }), 37 | toString() { 38 | return text.slice(this.range[0], this.range[1]) 39 | }, 40 | } 41 | return { expr: node, lastIndex: end } 42 | } 43 | 44 | start = regexThemeFn.lastIndex 45 | end = rb 46 | const node: nodes.ThemeFunctionNode = { 47 | type: nodes.NodeType.ThemeFunction, 48 | closed: true, 49 | range: [match.index, end + 1], 50 | valueRange: [start, end], 51 | value: parse_theme_val({ text, start, end }), 52 | toString() { 53 | return text.slice(this.range[0], this.range[1]) 54 | }, 55 | } 56 | return { expr: node, lastIndex: rb + 1 } 57 | } 58 | 59 | const regexThemePath = /(\[)|(\.[^.\s[]*)|([^.\s[]+)/gs 60 | 61 | export function parse_theme_val({ 62 | text, 63 | start = 0, 64 | end = text.length, 65 | }: { 66 | text: string 67 | start?: number 68 | end?: number 69 | }) { 70 | while (start < end && isSpace(text.charCodeAt(start))) start++ 71 | while (start < end && isSpace(text.charCodeAt(end - 1))) end-- 72 | 73 | // unquote 74 | if (text.charCodeAt(start) === 34 || text.charCodeAt(start) === 39) { 75 | if (text.charCodeAt(end - 1) === text.charCodeAt(start)) { 76 | end-- 77 | } 78 | start++ 79 | } else if (text.charCodeAt(end - 1) === 34 || text.charCodeAt(end - 1) === 39) { 80 | end-- 81 | } 82 | 83 | const node: nodes.ThemeValueNode = { 84 | type: nodes.NodeType.ThemeValue, 85 | range: [start, end], 86 | path: [], 87 | toString() { 88 | return text.slice(this.range[0], this.range[1]) 89 | }, 90 | } 91 | 92 | text = text.slice(0, end) 93 | while (start < end) { 94 | regexThemePath.lastIndex = start 95 | const match = regexThemePath.exec(text) 96 | if (match == null) break 97 | const [, leftSquareBracket, dotKey, firstKey] = match 98 | if (leftSquareBracket) { 99 | const rb = findRightBracket({ 100 | text, 101 | start, 102 | end, 103 | brackets: [91, 93], 104 | }) 105 | if (rb == undefined) { 106 | const a = match.index 107 | const b = end 108 | const n: nodes.ThemePathNode = { 109 | type: nodes.NodeType.ThemePath, 110 | range: [a, b], 111 | value: text.slice(a + 1, b), 112 | closed: false, 113 | toString() { 114 | return text.slice(this.range[0], this.range[1]) 115 | }, 116 | } 117 | node.path = node.path.concat(n) 118 | start = end 119 | continue 120 | } 121 | 122 | const a = match.index 123 | const b = rb + 1 124 | const n: nodes.ThemePathNode = { 125 | type: nodes.NodeType.ThemePath, 126 | range: [a, b], 127 | value: text.slice(a + 1, b - 1), 128 | closed: true, 129 | toString() { 130 | return text.slice(this.range[0], this.range[1]) 131 | }, 132 | } 133 | node.path = node.path.concat(n) 134 | start = rb + 1 135 | continue 136 | } 137 | 138 | if (dotKey) { 139 | const a = match.index 140 | const b = regexThemePath.lastIndex 141 | const n: nodes.ThemePathNode = { 142 | type: nodes.NodeType.ThemePath, 143 | range: [a, b], 144 | value: text.slice(a + 1, b), 145 | closed: true, 146 | toString() { 147 | return text.slice(this.range[0], this.range[1]) 148 | }, 149 | } 150 | node.path = node.path.concat(n) 151 | start = regexThemePath.lastIndex 152 | continue 153 | } 154 | 155 | if (firstKey) { 156 | const a = match.index 157 | const b = regexThemePath.lastIndex 158 | const n: nodes.ThemePathNode = { 159 | type: nodes.NodeType.ThemePath, 160 | range: [a, b], 161 | value: text.slice(a, b), 162 | closed: true, 163 | toString() { 164 | return text.slice(this.range[0], this.range[1]) 165 | }, 166 | } 167 | node.path = node.path.concat(n) 168 | start = regexThemePath.lastIndex 169 | continue 170 | } 171 | } 172 | 173 | return node 174 | } 175 | -------------------------------------------------------------------------------- /src/common/parser/spread.ts: -------------------------------------------------------------------------------- 1 | import * as nodes from "./nodes" 2 | import * as parser from "./parse_regexp" 3 | import { getVariant } from "./util" 4 | 5 | export type SpreadDescription = { 6 | target: nodes.Classname | nodes.ShortCss | nodes.ArbitraryClassname | nodes.ArbitraryProperty 7 | value: string 8 | variants: ReturnType[] 9 | important: boolean 10 | } 11 | 12 | export function spread(text: string, { separator = ":" }: { separator?: string } = {}) { 13 | interface Context { 14 | variants: ReturnType[] 15 | important: boolean 16 | } 17 | 18 | const items: SpreadDescription[] = [] 19 | const emptyGroup: nodes.Group[] = [] 20 | const emptyVariants: nodes.VariantSpan[] = [] 21 | const notClosed: nodes.BracketNode[] = [] 22 | 23 | const walk = (node: nodes.TwExpression | undefined, ctx: Context): void => { 24 | if (node == undefined) return 25 | 26 | if (nodes.NodeType.VariantSpan === node.type) { 27 | if (node.child == undefined) { 28 | emptyVariants.push(node) 29 | return 30 | } 31 | const variants = ctx.variants.slice() 32 | variants.push(getVariant(node.variant, separator)) 33 | walk(node.child, { ...ctx, variants }) 34 | } else if (nodes.NodeType.Group === node.type) { 35 | if (!node.closed) { 36 | notClosed.push(node) 37 | return 38 | } 39 | 40 | if (node.expressions.length === 0) { 41 | emptyGroup.push(node) 42 | return 43 | } 44 | 45 | node.expressions.forEach(n => walk(n, { ...ctx, important: ctx.important || node.important })) 46 | } else if (nodes.NodeType.ShortCss === node.type) { 47 | if (!node.closed) { 48 | notClosed.push(node) 49 | return 50 | } 51 | 52 | items.push({ 53 | target: node, 54 | value: text.slice(...node.range), 55 | ...ctx, 56 | important: ctx.important || node.important, 57 | }) 58 | } else if (nodes.NodeType.ArbitraryClassname === node.type) { 59 | if (!node.closed) { 60 | notClosed.push(node) 61 | return 62 | } 63 | 64 | if (node.e && node.e.type === nodes.NodeType.WithOpacity && node.e.closed === false) { 65 | notClosed.push(node.e) 66 | return 67 | } 68 | 69 | items.push({ 70 | target: node, 71 | value: text.slice(...node.range), 72 | ...ctx, 73 | important: ctx.important || node.important, 74 | }) 75 | } else if (nodes.NodeType.ArbitraryProperty === node.type) { 76 | if (!node.closed) { 77 | notClosed.push(node) 78 | return 79 | } 80 | 81 | items.push({ 82 | target: node, 83 | value: text.slice(...node.range), 84 | ...ctx, 85 | important: ctx.important || node.important, 86 | }) 87 | } else if (nodes.NodeType.ClassName === node.type) { 88 | items.push({ 89 | target: node, 90 | value: node.value, 91 | ...ctx, 92 | important: ctx.important || node.important, 93 | }) 94 | } 95 | } 96 | 97 | parser.setSeparator(separator) 98 | const program = parser.parse(text) 99 | program.expressions.forEach(expr => walk(expr, { variants: [], important: false })) 100 | 101 | return { items, emptyGroup, emptyVariants, notClosed } as const 102 | } 103 | -------------------------------------------------------------------------------- /src/common/parser/suggest.ts: -------------------------------------------------------------------------------- 1 | import * as nodes from "./nodes" 2 | import * as parser from "./parse_regexp" 3 | import { getVariant } from "./util" 4 | 5 | export interface SuggestionResult { 6 | target?: 7 | | nodes.Classname 8 | | nodes.ShortCss 9 | | nodes.ArbitraryClassname 10 | | nodes.ArbitraryProperty 11 | | nodes.SimpleVariant 12 | | nodes.ArbitrarySelector 13 | | nodes.ArbitraryVariant 14 | value: string 15 | variants: string[] 16 | inComment: boolean 17 | } 18 | 19 | export function suggest({ 20 | position, 21 | text, 22 | separator = ":", 23 | }: { 24 | position: number 25 | text: string 26 | separator?: string 27 | }): SuggestionResult { 28 | interface Context { 29 | variants: string[] 30 | } 31 | 32 | interface TravelResult extends Context { 33 | target?: 34 | | nodes.Classname 35 | | nodes.ShortCss 36 | | nodes.ArbitraryClassname 37 | | nodes.ArbitraryProperty 38 | | nodes.SimpleVariant 39 | | nodes.ArbitrarySelector 40 | | nodes.ArbitraryVariant 41 | } 42 | 43 | parser.setSeparator(separator) 44 | const result = travel(parser.parse(text, { breac: position }), { variants: [] }) 45 | 46 | if (!result.target) { 47 | return { 48 | variants: result.variants, 49 | value: "", 50 | inComment: inComment({ position, text }), 51 | } 52 | } 53 | 54 | return { 55 | ...result, 56 | value: result.target ? text.slice(result.target.range[0], result.target.range[1]) : "", 57 | inComment: inComment({ position, text }), 58 | } 59 | 60 | function inComment({ 61 | position, 62 | text, 63 | start = 0, 64 | end = text.length, 65 | }: { 66 | position: number 67 | text: string 68 | start?: number 69 | end?: number 70 | }): boolean { 71 | const reg = /(")|(')|(\/\/[^\n]*(?:\n|$))|((?:\/\*).*?(?:\*\/|$))/gs 72 | let match: RegExpExecArray | null 73 | reg.lastIndex = start 74 | text = text.slice(0, end) 75 | let isStringLeterial: false | '"' | "'" = false 76 | while ((match = reg.exec(text))) { 77 | const [, doubleQuote, singleQuote, lineComment, blockComment] = match 78 | if (doubleQuote) { 79 | if (!isStringLeterial) { 80 | isStringLeterial = '"' 81 | } else { 82 | isStringLeterial = false 83 | } 84 | } else if (singleQuote) { 85 | if (!isStringLeterial) { 86 | isStringLeterial = "'" 87 | } else { 88 | isStringLeterial = false 89 | } 90 | } else if (!isStringLeterial && (lineComment || blockComment)) { 91 | if (position >= match.index && position <= reg.lastIndex) { 92 | return true 93 | } 94 | } 95 | } 96 | 97 | return false 98 | } 99 | 100 | function inRange(node: nodes.Node) { 101 | return position >= node.range[0] && position <= node.range[1] 102 | } 103 | 104 | function travel(node: nodes.Node, ctx: Context): TravelResult { 105 | if (node == undefined) { 106 | return { variants: [] } 107 | } 108 | 109 | if (nodes.NodeType.Program === node.type) { 110 | const expr = node.expressions.find(inRange) 111 | if (expr) return walk(expr) 112 | return { ...ctx } 113 | } 114 | 115 | if (inRange(node)) return walk(node) 116 | 117 | return { ...ctx } 118 | 119 | function walk(node: nodes.Node) { 120 | if (nodes.NodeType.VariantSpan === node.type) { 121 | const variants = ctx.variants.slice() 122 | if (inRange(node.variant)) { 123 | if (position === node.variant.range[1]) variants.push(getVariant(node.variant, separator).value) 124 | return { target: node.variant, variants } 125 | } 126 | if (!node.child) return { variants: [] } 127 | variants.push(getVariant(node.variant, separator).value) 128 | return travel(node.child, { ...ctx, variants }) 129 | } 130 | 131 | if (nodes.NodeType.Group === node.type) { 132 | return travel( 133 | { type: nodes.NodeType.Program, expressions: node.expressions, range: node.range }, 134 | { ...ctx }, 135 | ) 136 | } 137 | 138 | if ( 139 | nodes.NodeType.ShortCss === node.type || 140 | nodes.NodeType.ArbitraryProperty === node.type || 141 | nodes.NodeType.ArbitraryClassname === node.type 142 | ) { 143 | return { target: node, variants: ctx.variants } 144 | } 145 | 146 | if (nodes.NodeType.ClassName === node.type) { 147 | return { target: node, variants: ctx.variants } 148 | } 149 | 150 | return { variants: [] } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/common/parser/theme.ts: -------------------------------------------------------------------------------- 1 | import { dlv } from "../get_set" 2 | import { NodeType, ThemePathNode } from "./nodes" 3 | import { parse_theme, parse_theme_val } from "./parse_theme" 4 | 5 | export function resolveThemeFunc(config: Tailwind.ResolvedConfigJS, value: string): string { 6 | let start = 0 7 | let ret = "" 8 | 9 | for (const node of parse_theme({ text: value })) { 10 | const result = theme(config, node.value.path) 11 | const val = result.value !== undefined ? renderThemeValue(result) : "" 12 | ret += value.slice(start, node.range[0]) + val 13 | start = node.range[1] 14 | } 15 | 16 | if (start < value.length) { 17 | ret += value.slice(start) 18 | } 19 | return ret.trim() 20 | } 21 | 22 | // for completions 23 | export function parseThemeValue({ 24 | config, 25 | useDefault, 26 | text, 27 | start = 0, 28 | end = text.length, 29 | }: { 30 | config: Tailwind.ResolvedConfigJS 31 | useDefault?: boolean 32 | text: string 33 | start?: number 34 | end?: number 35 | }) { 36 | const node = parse_theme_val({ text, start, end }) 37 | const value = resolvePath(config.theme, node.path, useDefault) 38 | if (value === undefined) { 39 | const ret = tryOpacityValue(node.path) 40 | if (ret.opacityValue) { 41 | node.path = ret.path 42 | } 43 | } 44 | return { path: node.path, range: node.range } 45 | } 46 | 47 | export function theme(config: Tailwind.ResolvedConfigJS, path: ThemePathNode[], useDefault = false) { 48 | let opacityValue: string | undefined 49 | let value = resolvePath(config.theme, path, useDefault) 50 | if (value === undefined) { 51 | const ret = tryOpacityValue(path) 52 | if (ret.opacityValue) { 53 | value = resolvePath(config.theme, ret.path, useDefault) 54 | opacityValue = ret.opacityValue 55 | path = ret.path 56 | } 57 | } 58 | return { value, opacityValue } 59 | } 60 | 61 | export function tryOpacityValue(path: ThemePathNode[]) { 62 | let opacityValue: string | undefined 63 | let arr = path.slice().reverse() 64 | let end: number | undefined 65 | for (let i = 0; i < arr.length; i++) { 66 | const n = arr[i] 67 | const x = n.value.lastIndexOf("/") 68 | if (x === -1) { 69 | if (end != undefined && end !== n.range[1]) { 70 | return { path } 71 | } 72 | end = n.range[0] 73 | opacityValue = n.toString() + (opacityValue ?? "") 74 | continue 75 | } 76 | 77 | opacityValue = n.value.slice(x + 1) + (opacityValue ?? "") 78 | 79 | const raw = n.toString() 80 | const k = raw.lastIndexOf("/") 81 | const rest = raw.slice(0, k) 82 | 83 | if (end != undefined && end !== n.range[1]) { 84 | if (k > 0 && k < raw.length - 1) { 85 | return { path } 86 | } 87 | } 88 | 89 | if (rest === "") { 90 | arr = arr.slice(i + 1) 91 | break 92 | } 93 | 94 | const t: ThemePathNode = { ...n, range: [n.range[0], n.range[1]] } 95 | t.value = n.value.slice(0, n.value.lastIndexOf("/")) 96 | t.range[1] = t.range[0] + k 97 | arr[i] = t 98 | arr = arr.slice(i) 99 | 100 | break 101 | } 102 | 103 | arr = arr.reverse() 104 | return { path: arr, opacityValue } 105 | } 106 | 107 | export function renderThemePath( 108 | config: Tailwind.ResolvedConfigJS, 109 | path: Array, 110 | useDefault = false, 111 | ): string { 112 | const keys = path.map(value => { 113 | if (typeof value !== "string") return value 114 | return { 115 | type: NodeType.ThemePath, 116 | closed: true, 117 | value, 118 | range: [0, value.length], 119 | toString() { 120 | return "." + value 121 | }, 122 | } 123 | }) 124 | return renderThemeValue(theme(config, keys, useDefault)) 125 | } 126 | 127 | export function resolvePath(obj: unknown, path: Array, useDefault = false): unknown { 128 | const keys = path.map(p => (typeof p === "string" ? p : p.value)) 129 | let value = dlv(obj, keys) 130 | if (useDefault && value?.["DEFAULT"] != undefined) { 131 | value = value["DEFAULT"] 132 | } 133 | return value 134 | } 135 | 136 | export function renderThemeValue({ value, opacityValue }: { value?: unknown; opacityValue?: string } = {}) { 137 | if (value == null) return `[${value}]` 138 | if (typeof value === "object") { 139 | if (Array.isArray(value)) return `Array[${value.join(", ")}]` 140 | return ( 141 | `Object{\n` + 142 | Object.keys(value) 143 | .map(k => `\t"${k}": "...",\n`) 144 | .join("") + 145 | "}\n" 146 | ) 147 | } 148 | if (typeof value === "function") { 149 | value = String(value({ opacityValue: opacityValue ?? "1" })) 150 | } 151 | if (opacityValue && typeof value === "string") { 152 | let replaced = false 153 | const result = value.replace("", _ => { 154 | replaced = true 155 | return opacityValue 156 | }) 157 | if (replaced) return result 158 | else { 159 | const match = 160 | /(\w+)\(\s*([\d.]+(?:%|deg|rad|grad|turn)?\s*,?\s*)([\d.]+(?:%|deg|rad|grad|turn)?\s*,?\s*)([\d.]+(?:%|deg|rad|grad|turn)?)/.exec( 161 | value, 162 | ) 163 | if (match == null) { 164 | const rgb = parseHexColor(value) 165 | if (rgb == null) return value 166 | const r = (rgb >> 16) & 0xff 167 | const g = (rgb >> 8) & 0xff 168 | const b = rgb & 0xff 169 | return `rgb(${r} ${g} ${b} / ${opacityValue})` 170 | } 171 | const [, fn, a, b, c] = match 172 | return `${fn}(${a.replaceAll(" ", "")} ${b.replaceAll(" ", "")} ${c.replaceAll(" ", "")}${ 173 | (a.indexOf(",") === -1 ? " / " : ", ") + opacityValue 174 | })` 175 | } 176 | } 177 | return String(value) 178 | } 179 | 180 | function parseHexColor(value: string): number | null { 181 | const result = /(?:^#?([A-Fa-f0-9]{6})(?:[A-Fa-f0-9]{2})?$)|(?:^#?([A-Fa-f0-9]{3})[A-Fa-f0-9]?$)/.exec(value) 182 | if (result !== null) { 183 | if (result[1]) { 184 | const v = parseInt(result[1], 16) 185 | return Number.isNaN(v) ? null : v 186 | } else if (result[2]) { 187 | const v = parseInt(result[2], 16) 188 | if (!Number.isNaN(v)) { 189 | let r = v & 0xf00 190 | let g = v & 0xf0 191 | let b = v & 0xf 192 | r = (r << 12) | (r << 8) 193 | g = (g << 8) | (g << 4) 194 | b = (b << 4) | b 195 | return r | g | b 196 | } 197 | } 198 | } 199 | return null 200 | } 201 | -------------------------------------------------------------------------------- /src/common/parser/util.ts: -------------------------------------------------------------------------------- 1 | import * as nodes from "./nodes" 2 | 3 | export function toKebab(value: string) { 4 | return value.replace(/\B[A-Z][a-z]*/g, s => "-" + s.toLowerCase()) 5 | } 6 | 7 | interface SimpleVariantToken extends nodes.TokenString { 8 | type: nodes.NodeType.SimpleVariant 9 | } 10 | 11 | interface ArbitrarySelectorToken extends nodes.TokenString { 12 | type: nodes.NodeType.ArbitrarySelector 13 | selector: nodes.CssSelector 14 | } 15 | 16 | interface ArbitraryVariantToken extends nodes.TokenString { 17 | type: nodes.NodeType.ArbitraryVariant 18 | selector: nodes.CssSelector 19 | } 20 | 21 | export function getVariant( 22 | variant: nodes.Variant, 23 | sep: string, 24 | ): SimpleVariantToken | ArbitrarySelectorToken | ArbitraryVariantToken { 25 | switch (variant.type) { 26 | case nodes.NodeType.ArbitrarySelector: 27 | return { 28 | type: nodes.NodeType.ArbitrarySelector, 29 | range: variant.selector.range, 30 | value: variant.selector.value, 31 | selector: variant.selector, 32 | } 33 | case nodes.NodeType.ArbitraryVariant: 34 | return { 35 | type: nodes.NodeType.ArbitraryVariant, 36 | range: [variant.range[0], variant.range[1] - sep.length], 37 | value: variant.prefix.value + "[" + variant.selector.value + "]", 38 | selector: variant.selector, 39 | } 40 | default: 41 | return { 42 | type: nodes.NodeType.SimpleVariant, 43 | range: variant.id.range, 44 | value: variant.id.value, 45 | } 46 | } 47 | } 48 | 49 | export function removeComments( 50 | source: string, 51 | keepSpaces = false, 52 | separator = ":", 53 | [start = 0, end = source.length] = [], 54 | ): string { 55 | const regexp = /(")|(')|(\[)|(\/\/[^\r\n]*(?:[^\r\n]|$))|((?:\/\*).*?(?:\*\/|$))/gs 56 | let match: RegExpExecArray | null 57 | regexp.lastIndex = start 58 | source = source.slice(0, end) 59 | let strings: 1 | 2 | undefined 60 | 61 | let buffer = "" 62 | while ((match = regexp.exec(source))) { 63 | const [, doubleQuote, singleQuote, bracket, lineComment, blockComment] = match 64 | 65 | let hasComment = false 66 | if (doubleQuote) { 67 | if (!strings) { 68 | strings = 1 69 | } else { 70 | strings = undefined 71 | } 72 | } else if (singleQuote) { 73 | if (!strings) { 74 | strings = 2 75 | } else { 76 | strings = undefined 77 | } 78 | } else if (bracket) { 79 | const rb = findRightBracket({ 80 | text: source, 81 | start: regexp.lastIndex - 1, 82 | brackets: [91, 93], 83 | end, 84 | comments: false, 85 | }) 86 | 87 | // TODO: Remove comments in arbitrary selectors only. 88 | if (rb) { 89 | let match = true 90 | for (let i = 0; i < separator.length; i++) { 91 | if (separator.charCodeAt(i) !== source.charCodeAt(rb + 1 + i)) { 92 | match = false 93 | break 94 | } 95 | } 96 | if (match && source[regexp.lastIndex - 2] !== "-") { 97 | buffer += source.slice(start, regexp.lastIndex) 98 | buffer += removeComments(source, keepSpaces, separator, [regexp.lastIndex, rb]) 99 | start = rb 100 | } 101 | } 102 | 103 | regexp.lastIndex = rb ? rb + 1 : end 104 | } else if (!strings && (lineComment || blockComment)) { 105 | hasComment = true 106 | } 107 | 108 | let data = source.slice(start, regexp.lastIndex) 109 | if (hasComment) { 110 | data = data.replace(lineComment || blockComment, match => { 111 | if (keepSpaces) { 112 | return "".padStart(match.length) 113 | } 114 | return "" 115 | }) 116 | } 117 | 118 | buffer += data 119 | start = regexp.lastIndex 120 | } 121 | 122 | if (start < end) { 123 | buffer += source.slice(start, end) 124 | } 125 | 126 | return buffer 127 | } 128 | 129 | /** Try to find right bracket from left bracket, return `undefind` if not found. */ 130 | export function findRightBracket({ 131 | text, 132 | start = 0, 133 | end = text.length, 134 | brackets = [40, 41], 135 | comments = true, 136 | }: { 137 | text: string 138 | start?: number 139 | end?: number 140 | brackets?: [number, number] 141 | comments?: boolean 142 | }): number | undefined { 143 | let stack = 0 144 | const [lbrac, rbrac] = brackets 145 | let comment = 0 146 | let string = 0 147 | let url = 0 148 | 149 | for (let i = start; i < end; i++) { 150 | const char = text.charCodeAt(i) 151 | if (char === lbrac) { 152 | if (string === 0 && comment === 0) { 153 | stack++ 154 | } 155 | } else if (char === rbrac) { 156 | if (string === 0 && comment === 0) { 157 | if (stack === 1) { 158 | return i 159 | } 160 | if (stack < 1) { 161 | return undefined 162 | } 163 | stack-- 164 | } 165 | } 166 | 167 | if (string === 0 && comment === 0) { 168 | if (url === 0 && char === 117 && /\W/.test(text[i - 1] || " ")) { 169 | url = 1 170 | } else if (url === 1 && char === 114) { 171 | url = 2 172 | } else if (url === 2 && char === 108) { 173 | url = 3 174 | } else if (url < 3 || (url === 3 && char === 41)) { 175 | url = 0 176 | } 177 | } 178 | 179 | if (comments) { 180 | if (url < 3 && comment === 0) { 181 | if (string === 0) { 182 | if (char === 47 && text.charCodeAt(i + 1) === 47) { 183 | comment = 1 184 | } else if (char === 47 && text.charCodeAt(i + 1) === 42) { 185 | comment = 2 186 | } 187 | } 188 | } else if (comment === 1 && char === 10) { 189 | comment = 0 190 | } else if (comment === 2 && char === 42 && text.charCodeAt(i + 1) === 47) { 191 | comment = 0 192 | i += 1 193 | } 194 | } 195 | 196 | if (string === 0) { 197 | if (comment === 0) { 198 | if (char === 34) { 199 | string = 1 200 | } else if (char === 39) { 201 | string = 2 202 | } 203 | } 204 | } else if (string === 1 && char === 34) { 205 | string = 0 206 | } else if (string === 2 && char === 39) { 207 | string = 0 208 | } 209 | } 210 | return undefined 211 | } 212 | 213 | export function isSpace(char: number) { 214 | if (Number.isNaN(char)) return true 215 | switch (char) { 216 | case 32: 217 | case 12: 218 | case 10: 219 | case 13: 220 | case 9: 221 | case 11: 222 | return true 223 | default: 224 | return false 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/common/plugins/_base.ts: -------------------------------------------------------------------------------- 1 | export interface Context { 2 | config: Tailwind.ResolvedConfigJS 3 | } 4 | 5 | export interface MatchPlugin { 6 | isMatch(value: string): boolean 7 | getName(): keyof Tailwind.CorePluginFeatures 8 | } 9 | 10 | export function hasDefault(obj: unknown): boolean { 11 | if (obj == undefined) return false 12 | return Object.prototype.hasOwnProperty.call(obj, "DEFAULT") 13 | } 14 | 15 | export function isCorePluginEnable(context: Context, key: keyof Tailwind.CorePluginFeatures): boolean { 16 | const { corePlugins } = context.config 17 | if (Array.isArray(corePlugins)) { 18 | return corePlugins.some(c => c === key) 19 | } 20 | if (typeof corePlugins === "object" && corePlugins !== null && corePlugins[key] === false) { 21 | return false 22 | } 23 | return true 24 | } 25 | 26 | export function isField(context: unknown, value: string): boolean { 27 | if (!context) return false 28 | if (!value) return false 29 | if (typeof context === "object") return Object.prototype.hasOwnProperty.call(context, value) 30 | return context === value 31 | } 32 | 33 | export function getPalette( 34 | context: Context, 35 | key: ("colors" | "fill" | "stroke" | `${string}Color${string}`) & keyof Tailwind.CorePluginFeatures, 36 | ): string[] | null { 37 | if (!isCorePluginEnable(context, key)) return null 38 | const palette = context.config.theme[key] as Tailwind.Palette 39 | const colorNames = Object.keys(flattenColorPalette(palette)) 40 | return colorNames 41 | } 42 | 43 | export function flattenColorPalette(colors: Tailwind.Palette | null | undefined): { 44 | [color: string]: Exclude 45 | } { 46 | return Object.assign( 47 | {}, 48 | ...Object.entries(colors ?? {}).flatMap(([color, values]) => { 49 | if (typeof values !== "object") { 50 | return [{ [`${color}`]: values }] 51 | } 52 | return Object.entries(flattenColorPalette(values as Tailwind.Palette | null | undefined)).map( 53 | ([keyword, colorValue]) => { 54 | return { 55 | [color + (keyword === "DEFAULT" ? "" : `-${keyword}`)]: colorValue, 56 | } 57 | }, 58 | ) 59 | }), 60 | ) 61 | } 62 | 63 | export function getOpacity( 64 | context: Context, 65 | key: ("opacity" | `${string}Opacity`) & keyof Tailwind.CorePluginFeatures = "opacity", 66 | ): string[] | null { 67 | return isCorePluginEnable(context, key) ? Object.keys(context.config.theme[key]) : null 68 | } 69 | -------------------------------------------------------------------------------- /src/common/plugins/backgrounds.ts: -------------------------------------------------------------------------------- 1 | import type { MatchPlugin } from "./_base" 2 | import { Context, getOpacity, getPalette, isCorePluginEnable, isField } from "./_base" 3 | import { Is, isArbitraryValue, isMatchColor } from "./_parse" 4 | 5 | export function backgroundAttachment(context: Context): MatchPlugin | null { 6 | if (!isCorePluginEnable(context, "backgroundAttachment")) return null 7 | return { 8 | getName() { 9 | return "backgroundAttachment" 10 | }, 11 | isMatch(value) { 12 | return value === "bg-fixed" || value === "bg-local" || value === "bg-scroll" 13 | }, 14 | } 15 | } 16 | 17 | export function backgroundClip(context: Context): MatchPlugin | null { 18 | if (!isCorePluginEnable(context, "backgroundClip")) return null 19 | return { 20 | getName() { 21 | return "backgroundClip" 22 | }, 23 | isMatch(value) { 24 | return ( 25 | value === "bg-clip-border" || 26 | value === "bg-clip-padding" || 27 | value === "bg-clip-content" || 28 | value === "bg-clip-text" 29 | ) 30 | }, 31 | } 32 | } 33 | 34 | export function backgroundColor(context: Context): MatchPlugin | null { 35 | const palette = getPalette(context, "backgroundColor") 36 | if (palette == null) return null 37 | const colors = new Set(palette) 38 | const o = getOpacity(context, "backgroundOpacity") 39 | const opacities = o ? new Set(o) : null 40 | return { 41 | getName() { 42 | return "backgroundColor" 43 | }, 44 | isMatch(value: string) { 45 | const match = /^bg-(.*)/s.exec(value) 46 | if (!match) return false 47 | return isMatchColor(match[1], colors, opacities, val => Is(context, val, "color")) 48 | }, 49 | } 50 | } 51 | 52 | export function backgroundOpacity(context: Context): MatchPlugin | null { 53 | if (!isCorePluginEnable(context, "backgroundOpacity")) return null 54 | return { 55 | getName() { 56 | return "backgroundOpacity" 57 | }, 58 | isMatch(value) { 59 | const match = /^bg-opacity-(.*)/s.exec(value) 60 | if (!match) return false 61 | const val = match[1] 62 | if (isArbitraryValue(val)) return true 63 | return isField(context.config.theme.backgroundOpacity, val) 64 | }, 65 | } 66 | } 67 | 68 | export function backgroundOrigin(context: Context): MatchPlugin | null { 69 | if (!isCorePluginEnable(context, "backgroundOrigin")) return null 70 | return { 71 | getName() { 72 | return "backgroundOrigin" 73 | }, 74 | isMatch(value) { 75 | return value === "bg-origin-border" || value === "bg-origin-padding" || value === "bg-origin-content" 76 | }, 77 | } 78 | } 79 | 80 | export function backgroundPosition(context: Context): MatchPlugin | null { 81 | if (!isCorePluginEnable(context, "backgroundPosition")) return null 82 | return { 83 | getName() { 84 | return "backgroundPosition" 85 | }, 86 | isMatch(value) { 87 | const match = /^-?bg-(.*)/s.exec(value) 88 | if (!match) return false 89 | let val = match[1] 90 | if (isArbitraryValue(val)) { 91 | val = val.slice(1, -1) 92 | return Is(context, val, "backgroundPosition") 93 | } 94 | return isField(context.config.theme.backgroundPosition, val) 95 | }, 96 | } 97 | } 98 | 99 | export function backgroundRepeat(context: Context): MatchPlugin | null { 100 | if (!isCorePluginEnable(context, "backgroundRepeat")) return null 101 | return { 102 | getName() { 103 | return "backgroundRepeat" 104 | }, 105 | isMatch(value) { 106 | return ( 107 | value === "bg-repeat" || 108 | value === "bg-no-repeat" || 109 | value === "bg-repeat-x" || 110 | value === "bg-repeat-y" || 111 | value === "bg-repeat-round" || 112 | value === "bg-repeat-space" 113 | ) 114 | }, 115 | } 116 | } 117 | 118 | export function backgroundSize(context: Context): MatchPlugin | null { 119 | if (!isCorePluginEnable(context, "backgroundSize")) return null 120 | return { 121 | getName() { 122 | return "backgroundSize" 123 | }, 124 | isMatch(value) { 125 | const match = /^bg-(.*)/s.exec(value) 126 | if (!match) return false 127 | let val = match[1] 128 | if (isArbitraryValue(val)) { 129 | val = val.slice(1, -1) 130 | return Is(context, val, "backgroundSize") 131 | } 132 | return isField(context.config.theme.backgroundSize, val) 133 | }, 134 | } 135 | } 136 | 137 | export function backgroundImage(context: Context): MatchPlugin | null { 138 | if (!isCorePluginEnable(context, "backgroundImage")) return null 139 | return { 140 | getName() { 141 | return "backgroundImage" 142 | }, 143 | isMatch(value) { 144 | const match = /^bg-(.*)/s.exec(value) 145 | if (!match) return false 146 | let val = match[1] 147 | if (isArbitraryValue(val)) { 148 | val = val.slice(1, -1).trim() 149 | return Is(context, val, "backgroundImage") 150 | } 151 | return isField(context.config.theme.backgroundImage, val) 152 | }, 153 | } 154 | } 155 | 156 | export function gradientColorStops(context: Context): MatchPlugin | null { 157 | if (!isCorePluginEnable(context, "gradientColorStops")) return null 158 | const palette = getPalette(context, "gradientColorStops") 159 | if (palette == null) return null 160 | const colors = new Set(palette) 161 | const o = getOpacity(context) 162 | const opacities = o ? new Set(o) : null 163 | return { 164 | getName() { 165 | return "gradientColorStops" 166 | }, 167 | isMatch(value) { 168 | const match = /^(?:from|to|via)(?:-|\b)(.*)/s.exec(value) 169 | if (!match) return false 170 | return isMatchColor(match[1], colors, opacities, val => Is(context, val, "color")) 171 | }, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/common/plugins/content.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function content(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "content")) return null 6 | const _hasDefault = hasDefault(context.config.theme.content) 7 | return { 8 | getName() { 9 | return "content" 10 | }, 11 | isMatch(value: string) { 12 | const match = /^content-(.*)/s.exec(value) 13 | if (!match) return false 14 | const val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) return true 17 | return isField(context.config.theme.content, val) 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/plugins/effects.ts: -------------------------------------------------------------------------------- 1 | import { Context, getOpacity, getPalette, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { Is, isArbitraryValue, isMatchColor } from "./_parse" 3 | 4 | export function boxShadow(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "boxShadow")) return null 6 | const _hasDefault = hasDefault(context.config.theme.boxShadow) 7 | return { 8 | getName() { 9 | return "boxShadow" 10 | }, 11 | isMatch(value) { 12 | const match = /^shadow(?:-|\b)(.*)/s.exec(value) 13 | if (!match) return false 14 | let val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) { 17 | val = val.slice(1, -1).trim() 18 | return Is(context, val, "shadow") 19 | } 20 | return isField(context.config.theme.boxShadow, val) 21 | }, 22 | } 23 | } 24 | 25 | export function boxShadowColor(context: Context): MatchPlugin | null { 26 | const palette = getPalette(context, "boxShadowColor") 27 | if (palette == null) return null 28 | const colors = new Set(palette) 29 | const o = getOpacity(context) 30 | const opacities = o ? new Set(o) : null 31 | return { 32 | getName() { 33 | return "boxShadowColor" 34 | }, 35 | isMatch(value: string) { 36 | const match = /^shadow-(.*)/s.exec(value) 37 | if (!match) return false 38 | return isMatchColor(match[1], colors, opacities, val => Is(context, val, "color")) 39 | }, 40 | } 41 | } 42 | 43 | export function opacity(context: Context): MatchPlugin | null { 44 | if (!isCorePluginEnable(context, "opacity")) return null 45 | return { 46 | getName() { 47 | return "opacity" 48 | }, 49 | isMatch(value: string) { 50 | const match = /^opacity-(.*)/s.exec(value) 51 | if (!match) return false 52 | const val = match[1] 53 | if (isArbitraryValue(val)) return true 54 | return isField(context.config.theme.opacity, val) 55 | }, 56 | } 57 | } 58 | 59 | export function mixBlendMode(context: Context): MatchPlugin | null { 60 | if (!isCorePluginEnable(context, "mixBlendMode")) return null 61 | return { 62 | getName() { 63 | return "mixBlendMode" 64 | }, 65 | isMatch(value) { 66 | const match = /^mix-blend-(.*)/s.exec(value) 67 | if (!match) return false 68 | const val = match[1] 69 | return ( 70 | val === "normal" || 71 | val === "multiply" || 72 | val === "screen" || 73 | val === "overlay" || 74 | val === "darken" || 75 | val === "lighten" || 76 | val === "color-dodge" || 77 | val === "color-burn" || 78 | val === "hard-light" || 79 | val === "soft-light" || 80 | val === "difference" || 81 | val === "exclusion" || 82 | val === "hue" || 83 | val === "saturation" || 84 | val === "color" || 85 | val === "luminosity" || 86 | val === "plus-lighter" 87 | ) 88 | }, 89 | } 90 | } 91 | 92 | export function backgroundBlendMode(context: Context): MatchPlugin | null { 93 | if (!isCorePluginEnable(context, "backgroundBlendMode")) return null 94 | return { 95 | getName() { 96 | return "backgroundBlendMode" 97 | }, 98 | isMatch(value) { 99 | const match = /^bg-blend-(.*)/s.exec(value) 100 | if (!match) return false 101 | const val = match[1] 102 | return ( 103 | val === "normal" || 104 | val === "multiply" || 105 | val === "screen" || 106 | val === "overlay" || 107 | val === "darken" || 108 | val === "lighten" || 109 | val === "color-dodge" || 110 | val === "color-burn" || 111 | val === "hard-light" || 112 | val === "soft-light" || 113 | val === "difference" || 114 | val === "exclusion" || 115 | val === "hue" || 116 | val === "saturation" || 117 | val === "color" || 118 | val === "luminosity" 119 | ) 120 | }, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/common/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import * as plugins from "./plugins" 2 | 3 | interface TrimPrefix { 4 | (classname: string): string 5 | } 6 | 7 | function isExist(value: T | null | undefined): value is T { 8 | return !!value 9 | } 10 | 11 | export function createGetPluginByName(config: Tailwind.ResolvedConfigJS) { 12 | const corePlugins = Object.values(plugins) 13 | .map(build => build({ config })) 14 | .filter(isExist) 15 | return (value: string, trimPrefix?: TrimPrefix) => { 16 | if (trimPrefix) value = trimPrefix(value) 17 | for (const plugin of corePlugins) { 18 | if (plugin.isMatch(value)) return plugin 19 | } 20 | return undefined 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/plugins/plugins.ts: -------------------------------------------------------------------------------- 1 | export * from "./backgrounds" 2 | export * from "./borders" 3 | export * from "./content" 4 | export * from "./effects" 5 | export * from "./filters" 6 | export * from "./flexbox" 7 | export * from "./interactivity" 8 | export * from "./layout" 9 | export * from "./sizing" 10 | export * from "./spacing" 11 | export * from "./svg" 12 | export * from "./tables" 13 | export * from "./transforms" 14 | export * from "./transitions" 15 | export * from "./typography" 16 | -------------------------------------------------------------------------------- /src/common/plugins/sizing.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function width(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "width")) return null 6 | const _hasDefault = hasDefault(context.config.theme.width) 7 | return { 8 | getName() { 9 | return "width" 10 | }, 11 | isMatch(value) { 12 | const match = /^w(?:-|\b)(.*)/s.exec(value) 13 | if (!match) return false 14 | const val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) return true 17 | return isField(context.config.theme.width, val) 18 | }, 19 | } 20 | } 21 | 22 | export function minWidth(context: Context): MatchPlugin | null { 23 | if (!isCorePluginEnable(context, "minWidth")) return null 24 | const _hasDefault = hasDefault(context.config.theme.minWidth) 25 | return { 26 | getName() { 27 | return "minWidth" 28 | }, 29 | isMatch(value) { 30 | const match = /^min-w(?:-|\b)(.*)/s.exec(value) 31 | if (!match) return false 32 | const val = match[1] 33 | if (val === "") return _hasDefault 34 | if (isArbitraryValue(val)) return true 35 | return isField(context.config.theme.minWidth, val) 36 | }, 37 | } 38 | } 39 | 40 | export function maxWidth(context: Context): MatchPlugin | null { 41 | if (!isCorePluginEnable(context, "maxWidth")) return null 42 | const _hasDefault = hasDefault(context.config.theme.maxWidth) 43 | return { 44 | getName() { 45 | return "maxWidth" 46 | }, 47 | isMatch(value) { 48 | const match = /^max-w(?:-|\b)(.*)/s.exec(value) 49 | if (!match) return false 50 | const val = match[1] 51 | if (val === "") return _hasDefault 52 | if (isArbitraryValue(val)) return true 53 | return isField(context.config.theme.maxWidth, val) 54 | }, 55 | } 56 | } 57 | 58 | export function height(context: Context): MatchPlugin | null { 59 | if (!isCorePluginEnable(context, "height")) return null 60 | const _hasDefault = hasDefault(context.config.theme.height) 61 | return { 62 | getName() { 63 | return "height" 64 | }, 65 | isMatch(value) { 66 | const match = /^h(?:-|\b)(.*)/s.exec(value) 67 | if (!match) return false 68 | const val = match[1] 69 | if (val === "") return _hasDefault 70 | if (isArbitraryValue(val)) return true 71 | return isField(context.config.theme.height, val) 72 | }, 73 | } 74 | } 75 | 76 | export function minHeight(context: Context): MatchPlugin | null { 77 | if (!isCorePluginEnable(context, "minHeight")) return null 78 | const _hasDefault = hasDefault(context.config.theme.minHeight) 79 | return { 80 | getName() { 81 | return "minHeight" 82 | }, 83 | isMatch(value) { 84 | const match = /^min-h(?:-|\b)(.*)/s.exec(value) 85 | if (!match) return false 86 | const val = match[1] 87 | if (val === "") return _hasDefault 88 | if (isArbitraryValue(val)) return true 89 | return isField(context.config.theme.minHeight, val) 90 | }, 91 | } 92 | } 93 | 94 | export function maxHeight(context: Context): MatchPlugin | null { 95 | if (!isCorePluginEnable(context, "maxHeight")) return null 96 | const _hasDefault = hasDefault(context.config.theme.maxHeight) 97 | return { 98 | getName() { 99 | return "maxHeight" 100 | }, 101 | isMatch(value) { 102 | const match = /^max-h(?:-|\b)(.*)/s.exec(value) 103 | if (!match) return false 104 | const val = match[1] 105 | if (val === "") return _hasDefault 106 | if (isArbitraryValue(val)) return true 107 | return isField(context.config.theme.maxHeight, val) 108 | }, 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/common/plugins/spacing.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function padding(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "padding")) return null 6 | const _hasDefault = hasDefault(context.config.theme.padding) 7 | return { 8 | getName() { 9 | return "padding" 10 | }, 11 | isMatch(value) { 12 | const match = /^(?:pt|pr|pb|pl|px|py|p)(?:-|\b)(.*)/s.exec(value) 13 | if (!match) return false 14 | const val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) return true 17 | return isField(context.config.theme.padding, val) 18 | }, 19 | } 20 | } 21 | 22 | export function margin(context: Context): MatchPlugin | null { 23 | if (!isCorePluginEnable(context, "margin")) return null 24 | const _hasDefault = hasDefault(context.config.theme.margin) 25 | return { 26 | getName() { 27 | return "margin" 28 | }, 29 | isMatch(value) { 30 | const match = /^-?(?:mt|mr|mb|ml|mx|my|m)(?:-|\b)(.*)/s.exec(value) 31 | if (!match) return false 32 | const val = match[1] 33 | if (val === "") return _hasDefault 34 | if (isArbitraryValue(val)) return true 35 | return isField(context.config.theme.margin, val) 36 | }, 37 | } 38 | } 39 | 40 | export function space(context: Context): MatchPlugin | null { 41 | if (!isCorePluginEnable(context, "space")) return null 42 | const _hasDefault = hasDefault(context.config.theme.space) 43 | return { 44 | getName() { 45 | return "space" 46 | }, 47 | isMatch(value) { 48 | const match = /^-?space-(?:x|y)(?:-|\b)(.*)/s.exec(value) 49 | if (!match) return false 50 | const val = match[1] 51 | if (val === "") return _hasDefault 52 | if (val === "reverse") return true 53 | if (isArbitraryValue(val)) return true 54 | return isField(context.config.theme.space, val) 55 | }, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/plugins/svg.ts: -------------------------------------------------------------------------------- 1 | import { Context, getOpacity, getPalette, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { Is, isArbitraryValue, isMatchColor } from "./_parse" 3 | 4 | export function fill(context: Context): MatchPlugin | null { 5 | const palette = getPalette(context, "fill") 6 | if (palette == null) return null 7 | const colors = new Set(palette) 8 | const o = getOpacity(context) 9 | const opacities = o ? new Set(o) : null 10 | return { 11 | getName() { 12 | return "fill" 13 | }, 14 | isMatch(value: string) { 15 | const match = /^fill-(.*)/s.exec(value) 16 | if (!match) return false 17 | return isMatchColor(match[1], colors, opacities, val => 18 | Is(context, val, "empty", "url", "var", "color", "none"), 19 | ) 20 | }, 21 | } 22 | } 23 | 24 | export function stroke(context: Context): MatchPlugin | null { 25 | const palette = getPalette(context, "stroke") 26 | if (palette == null) return null 27 | const colors = new Set(palette) 28 | const o = getOpacity(context) 29 | const opacities = o ? new Set(o) : null 30 | return { 31 | getName() { 32 | return "stroke" 33 | }, 34 | isMatch(value: string) { 35 | const match = /^stroke-(.*)/s.exec(value) 36 | if (!match) return false 37 | return isMatchColor(match[1], colors, opacities, val => Is(context, val, "url", "color")) 38 | }, 39 | } 40 | } 41 | 42 | export function strokeWidth(context: Context): MatchPlugin | null { 43 | if (!isCorePluginEnable(context, "strokeWidth")) return null 44 | const _hasDefault = hasDefault(context.config.theme.strokeWidth) 45 | return { 46 | getName() { 47 | return "strokeWidth" 48 | }, 49 | isMatch(value) { 50 | const match = /^stroke(?:-|\b)(.*)/s.exec(value) 51 | if (!match) return false 52 | let val = match[1] 53 | if (val === "") return _hasDefault 54 | if (isArbitraryValue(val)) { 55 | val = val.slice(1, -1) 56 | return Is(context, val, "length", "percentage", "number") 57 | } 58 | return isField(context.config.theme.strokeWidth, val) 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/common/plugins/tables.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function borderCollapse(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "borderCollapse")) return null 6 | return { 7 | getName() { 8 | return "borderCollapse" 9 | }, 10 | isMatch(value) { 11 | return value === "border-collapse" || value === "border-separate" 12 | }, 13 | } 14 | } 15 | 16 | export function tableLayout(context: Context): MatchPlugin | null { 17 | if (!isCorePluginEnable(context, "tableLayout")) return null 18 | return { 19 | getName() { 20 | return "tableLayout" 21 | }, 22 | isMatch(value) { 23 | return value === "table-auto" || value === "table-fixed" 24 | }, 25 | } 26 | } 27 | 28 | export function borderSpacing(context: Context): MatchPlugin | null { 29 | if (!isCorePluginEnable(context, "borderSpacing")) return null 30 | const _hasDefault = hasDefault(context.config.theme.borderSpacing) 31 | return { 32 | getName() { 33 | return "borderSpacing" 34 | }, 35 | isMatch(value) { 36 | const match = /^border-spacing(?:-x|-y|\b)-(.*)/s.exec(value) 37 | if (!match) return false 38 | const val = match[1] 39 | if (val === "") return _hasDefault 40 | if (isArbitraryValue(val)) return true 41 | return isField(context.config.theme.borderSpacing, val) 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/common/plugins/tests/flattenColorPalette.spec.ts: -------------------------------------------------------------------------------- 1 | import resolveConfig from "tailwindcss/resolveConfig" 2 | import { flattenColorPalette } from "../_base" 3 | 4 | it("flattenColorPalette", () => { 5 | const config = resolveConfig({ 6 | theme: { 7 | extend: { 8 | colors: { 9 | "foo-5": "#056660", 10 | "foo-5/10": "#051060", 11 | "foo-5/10/10%": "#651025", 12 | space: { 13 | "1/1": "#051025", 14 | }, 15 | }, 16 | }, 17 | }, 18 | }) 19 | 20 | const colors = Object.keys(flattenColorPalette(config.theme.colors)) 21 | colors.sort((a, b) => a.localeCompare(b)) 22 | 23 | expect(colors).toEqual([ 24 | "amber-100", 25 | "amber-200", 26 | "amber-300", 27 | "amber-400", 28 | "amber-50", 29 | "amber-500", 30 | "amber-600", 31 | "amber-700", 32 | "amber-800", 33 | "amber-900", 34 | "black", 35 | "blue-100", 36 | "blue-200", 37 | "blue-300", 38 | "blue-400", 39 | "blue-50", 40 | "blue-500", 41 | "blue-600", 42 | "blue-700", 43 | "blue-800", 44 | "blue-900", 45 | "current", 46 | "cyan-100", 47 | "cyan-200", 48 | "cyan-300", 49 | "cyan-400", 50 | "cyan-50", 51 | "cyan-500", 52 | "cyan-600", 53 | "cyan-700", 54 | "cyan-800", 55 | "cyan-900", 56 | "emerald-100", 57 | "emerald-200", 58 | "emerald-300", 59 | "emerald-400", 60 | "emerald-50", 61 | "emerald-500", 62 | "emerald-600", 63 | "emerald-700", 64 | "emerald-800", 65 | "emerald-900", 66 | "foo-5", 67 | "foo-5/10", 68 | "foo-5/10/10%", 69 | "fuchsia-100", 70 | "fuchsia-200", 71 | "fuchsia-300", 72 | "fuchsia-400", 73 | "fuchsia-50", 74 | "fuchsia-500", 75 | "fuchsia-600", 76 | "fuchsia-700", 77 | "fuchsia-800", 78 | "fuchsia-900", 79 | "gray-100", 80 | "gray-200", 81 | "gray-300", 82 | "gray-400", 83 | "gray-50", 84 | "gray-500", 85 | "gray-600", 86 | "gray-700", 87 | "gray-800", 88 | "gray-900", 89 | "green-100", 90 | "green-200", 91 | "green-300", 92 | "green-400", 93 | "green-50", 94 | "green-500", 95 | "green-600", 96 | "green-700", 97 | "green-800", 98 | "green-900", 99 | "indigo-100", 100 | "indigo-200", 101 | "indigo-300", 102 | "indigo-400", 103 | "indigo-50", 104 | "indigo-500", 105 | "indigo-600", 106 | "indigo-700", 107 | "indigo-800", 108 | "indigo-900", 109 | "inherit", 110 | "lime-100", 111 | "lime-200", 112 | "lime-300", 113 | "lime-400", 114 | "lime-50", 115 | "lime-500", 116 | "lime-600", 117 | "lime-700", 118 | "lime-800", 119 | "lime-900", 120 | "neutral-100", 121 | "neutral-200", 122 | "neutral-300", 123 | "neutral-400", 124 | "neutral-50", 125 | "neutral-500", 126 | "neutral-600", 127 | "neutral-700", 128 | "neutral-800", 129 | "neutral-900", 130 | "orange-100", 131 | "orange-200", 132 | "orange-300", 133 | "orange-400", 134 | "orange-50", 135 | "orange-500", 136 | "orange-600", 137 | "orange-700", 138 | "orange-800", 139 | "orange-900", 140 | "pink-100", 141 | "pink-200", 142 | "pink-300", 143 | "pink-400", 144 | "pink-50", 145 | "pink-500", 146 | "pink-600", 147 | "pink-700", 148 | "pink-800", 149 | "pink-900", 150 | "purple-100", 151 | "purple-200", 152 | "purple-300", 153 | "purple-400", 154 | "purple-50", 155 | "purple-500", 156 | "purple-600", 157 | "purple-700", 158 | "purple-800", 159 | "purple-900", 160 | "red-100", 161 | "red-200", 162 | "red-300", 163 | "red-400", 164 | "red-50", 165 | "red-500", 166 | "red-600", 167 | "red-700", 168 | "red-800", 169 | "red-900", 170 | "rose-100", 171 | "rose-200", 172 | "rose-300", 173 | "rose-400", 174 | "rose-50", 175 | "rose-500", 176 | "rose-600", 177 | "rose-700", 178 | "rose-800", 179 | "rose-900", 180 | "sky-100", 181 | "sky-200", 182 | "sky-300", 183 | "sky-400", 184 | "sky-50", 185 | "sky-500", 186 | "sky-600", 187 | "sky-700", 188 | "sky-800", 189 | "sky-900", 190 | "slate-100", 191 | "slate-200", 192 | "slate-300", 193 | "slate-400", 194 | "slate-50", 195 | "slate-500", 196 | "slate-600", 197 | "slate-700", 198 | "slate-800", 199 | "slate-900", 200 | "space-1/1", 201 | "stone-100", 202 | "stone-200", 203 | "stone-300", 204 | "stone-400", 205 | "stone-50", 206 | "stone-500", 207 | "stone-600", 208 | "stone-700", 209 | "stone-800", 210 | "stone-900", 211 | "teal-100", 212 | "teal-200", 213 | "teal-300", 214 | "teal-400", 215 | "teal-50", 216 | "teal-500", 217 | "teal-600", 218 | "teal-700", 219 | "teal-800", 220 | "teal-900", 221 | "transparent", 222 | "violet-100", 223 | "violet-200", 224 | "violet-300", 225 | "violet-400", 226 | "violet-50", 227 | "violet-500", 228 | "violet-600", 229 | "violet-700", 230 | "violet-800", 231 | "violet-900", 232 | "white", 233 | "yellow-100", 234 | "yellow-200", 235 | "yellow-300", 236 | "yellow-400", 237 | "yellow-50", 238 | "yellow-500", 239 | "yellow-600", 240 | "yellow-700", 241 | "yellow-800", 242 | "yellow-900", 243 | "zinc-100", 244 | "zinc-200", 245 | "zinc-300", 246 | "zinc-400", 247 | "zinc-50", 248 | "zinc-500", 249 | "zinc-600", 250 | "zinc-700", 251 | "zinc-800", 252 | "zinc-900", 253 | ]) 254 | }) 255 | -------------------------------------------------------------------------------- /src/common/plugins/transforms.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function scale(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "scale")) return null 6 | const _hasDefault = hasDefault(context.config.theme.scale) 7 | return { 8 | getName() { 9 | return "scale" 10 | }, 11 | isMatch(value) { 12 | const match = /^-?scale(?:-x|-y)?(?:-|\b)(.*)/s.exec(value) 13 | if (!match) return false 14 | const val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) return true 17 | return isField(context.config.theme.scale, val) 18 | }, 19 | } 20 | } 21 | 22 | export function rotate(context: Context): MatchPlugin | null { 23 | if (!isCorePluginEnable(context, "rotate")) return null 24 | const _hasDefault = hasDefault(context.config.theme.rotate) 25 | return { 26 | getName() { 27 | return "rotate" 28 | }, 29 | isMatch(value) { 30 | const match = /^-?rotate(?:-|\b)(.*)/s.exec(value) 31 | if (!match) return false 32 | const val = match[1] 33 | if (val === "") return _hasDefault 34 | if (isArbitraryValue(val)) return true 35 | return isField(context.config.theme.rotate, val) 36 | }, 37 | } 38 | } 39 | 40 | export function translate(context: Context): MatchPlugin | null { 41 | if (!isCorePluginEnable(context, "translate")) return null 42 | const _hasDefault = hasDefault(context.config.theme.translate) 43 | return { 44 | getName() { 45 | return "translate" 46 | }, 47 | isMatch(value) { 48 | const match = /^-?translate-(?:x|y)(?:-|\b)(.*)/s.exec(value) 49 | if (!match) return false 50 | const val = match[1] 51 | if (val === "") return _hasDefault 52 | if (isArbitraryValue(val)) return true 53 | return isField(context.config.theme.translate, val) 54 | }, 55 | } 56 | } 57 | 58 | export function skew(context: Context): MatchPlugin | null { 59 | if (!isCorePluginEnable(context, "skew")) return null 60 | const _hasDefault = hasDefault(context.config.theme.skew) 61 | return { 62 | getName() { 63 | return "skew" 64 | }, 65 | isMatch(value) { 66 | const match = /^-?skew-(?:x|y)(?:-|\b)(.*)/s.exec(value) 67 | if (!match) return false 68 | const val = match[1] 69 | if (val === "") return _hasDefault 70 | if (isArbitraryValue(val)) return true 71 | return isField(context.config.theme.skew, val) 72 | }, 73 | } 74 | } 75 | 76 | export function transformOrigin(context: Context): MatchPlugin | null { 77 | if (!isCorePluginEnable(context, "transformOrigin")) return null 78 | return { 79 | getName() { 80 | return "transformOrigin" 81 | }, 82 | isMatch(value) { 83 | const match = /^origin-(.*)/s.exec(value) 84 | if (!match) return false 85 | const val = match[1] 86 | if (isArbitraryValue(val)) return true 87 | return ( 88 | val === "center" || 89 | val === "top" || 90 | val === "top-right" || 91 | val === "right" || 92 | val === "bottom-right" || 93 | val === "bottom" || 94 | val === "bottom-left" || 95 | val === "left" || 96 | val === "left" || 97 | val === "top-left" 98 | ) 99 | }, 100 | } 101 | } 102 | 103 | export function transform(context: Context): MatchPlugin | null { 104 | if (!isCorePluginEnable(context, "transform")) return null 105 | return { 106 | getName() { 107 | return "transform" 108 | }, 109 | isMatch(value) { 110 | return ( 111 | value === "transform" || 112 | value === "transform-cpu" || 113 | value === "transform-gpu" || 114 | value === "transform-none" 115 | ) 116 | }, 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/common/plugins/transitions.ts: -------------------------------------------------------------------------------- 1 | import { Context, hasDefault, isCorePluginEnable, isField, MatchPlugin } from "./_base" 2 | import { isArbitraryValue } from "./_parse" 3 | 4 | export function animation(context: Context): MatchPlugin | null { 5 | if (!isCorePluginEnable(context, "animation")) return null 6 | const _hasDefault = hasDefault(context.config.theme.animation) 7 | return { 8 | getName() { 9 | return "animation" 10 | }, 11 | isMatch(value) { 12 | const match = /^animate(?:-|\b)(.*)/s.exec(value) 13 | if (!match) return false 14 | const val = match[1] 15 | if (val === "") return _hasDefault 16 | if (isArbitraryValue(val)) return true 17 | return isField(context.config.theme.animation, val) 18 | }, 19 | } 20 | } 21 | 22 | export function transitionDuration(context: Context): MatchPlugin | null { 23 | if (!isCorePluginEnable(context, "transitionDuration")) return null 24 | const _hasDefault = hasDefault(context.config.theme.transitionDuration) 25 | return { 26 | getName() { 27 | return "transitionDuration" 28 | }, 29 | isMatch(value) { 30 | const match = /^duration(?:-|\b)(.*)/s.exec(value) 31 | if (!match) return false 32 | const val = match[1] 33 | if (val === "") return _hasDefault 34 | const isNegative = match[0].charCodeAt(0) === 45 35 | if (isArbitraryValue(val)) return !isNegative 36 | return isField(context.config.theme.transitionDuration, val) 37 | }, 38 | } 39 | } 40 | 41 | export function transitionDelay(context: Context): MatchPlugin | null { 42 | if (!isCorePluginEnable(context, "transitionDelay")) return null 43 | const _hasDefault = hasDefault(context.config.theme.transitionDelay) 44 | return { 45 | getName() { 46 | return "transitionDelay" 47 | }, 48 | isMatch(value) { 49 | const match = /^delay(?:-|\b)(.*)/s.exec(value) 50 | if (!match) return false 51 | const val = match[1] 52 | if (val === "") return _hasDefault 53 | const isNegative = match[0].charCodeAt(0) === 45 54 | if (isArbitraryValue(val)) return !isNegative 55 | return isField(context.config.theme.transitionDelay, val) 56 | }, 57 | } 58 | } 59 | 60 | export function transitionProperty(context: Context): MatchPlugin | null { 61 | if (!isCorePluginEnable(context, "transitionProperty")) return null 62 | const _hasDefault = hasDefault(context.config.theme.transitionProperty) 63 | return { 64 | getName() { 65 | return "transitionProperty" 66 | }, 67 | isMatch(value) { 68 | const match = /^-?transition(?:-|\b)(.*)/s.exec(value) 69 | if (!match) return false 70 | const val = match[1] 71 | if (val === "") return _hasDefault 72 | if (isArbitraryValue(val)) return true 73 | return isField(context.config.theme.transitionProperty, val) 74 | }, 75 | } 76 | } 77 | 78 | export function transitionTimingFunction(context: Context): MatchPlugin | null { 79 | if (!isCorePluginEnable(context, "transitionTimingFunction")) return null 80 | const _hasDefault = hasDefault(context.config.theme.transitionTimingFunction) 81 | return { 82 | getName() { 83 | return "transitionTimingFunction" 84 | }, 85 | isMatch(value) { 86 | const match = /^ease(?:-|\b)(.*)/s.exec(value) 87 | if (!match) return false 88 | const val = match[1] 89 | if (val === "") return _hasDefault 90 | if (isArbitraryValue(val)) return true 91 | return isField(context.config.theme.transitionTimingFunction, val) 92 | }, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/common/pnp.ts: -------------------------------------------------------------------------------- 1 | import type { PnpApi } from "@yarnpkg/pnp" 2 | import fs from "fs" 3 | import Module from "module" 4 | import path from "path" 5 | import { fileURLToPath, URL } from "url" 6 | export interface PackageLocator { 7 | name: string 8 | reference: string 9 | } 10 | 11 | export interface PackageInformation { 12 | packageLocation: string 13 | packageDependencies: Map 14 | packagePeers: Set 15 | linkType: "HARD" | "SOFT" 16 | } 17 | 18 | interface FindPnpApiReturnValue extends PnpApi { 19 | setup: () => void 20 | } 21 | 22 | export function findPnpApi(lookupSource: URL | string): FindPnpApiReturnValue | undefined { 23 | const lookupPath = lookupSource instanceof URL ? fileURLToPath(lookupSource) : lookupSource 24 | return findContext(lookupPath) 25 | 26 | function findContext(workspace: string): FindPnpApiReturnValue | undefined { 27 | try { 28 | let pnpPath = path.resolve(workspace, ".pnp") 29 | 30 | if ( 31 | !path.isAbsolute(pnpPath) && 32 | !pnpPath.startsWith("." + path.sep) && 33 | !pnpPath.startsWith(".." + path.sep) 34 | ) { 35 | pnpPath = "." + path.sep + pnpPath 36 | } 37 | 38 | if (isExist(pnpPath + ".cjs")) pnpPath += ".cjs" 39 | else if (isExist(pnpPath + ".js")) pnpPath += ".js" 40 | 41 | // @ts-ignore TS/7016 42 | const filename = Module["_resolveFilename"](pnpPath, { paths: Module["_nodeModulePaths"](workspace) }) 43 | const module = new Module("") 44 | return module.require(filename) 45 | } catch {} 46 | 47 | return undefined 48 | } 49 | } 50 | 51 | export function isExist(filename: string) { 52 | try { 53 | fs.accessSync(filename) 54 | return true 55 | } catch { 56 | return false 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/common/unquote.ts: -------------------------------------------------------------------------------- 1 | export function unquote(value: string) { 2 | if (value.length < 2) return value 3 | const quote = value[0] 4 | if (quote !== value[value.length - 1]) return value 5 | if (quote != '"' && quote != "'") return value 6 | return value.slice(1, -1) 7 | } 8 | -------------------------------------------------------------------------------- /src/common/vscode-css-languageservice/index.ts: -------------------------------------------------------------------------------- 1 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 2 | export const cssDataManager = new CSSDataManager({ useDefaultDataProvider: true }) 3 | -------------------------------------------------------------------------------- /src/locale.ts: -------------------------------------------------------------------------------- 1 | interface NLSConfig { 2 | locale: "en" | "zh-tw" 3 | } 4 | 5 | const nlsConfig = JSON.parse(process.env.VSCODE_NLS_CONFIG ?? "") as NLSConfig 6 | 7 | import { createIntl, createIntlCache, IntlConfig } from "@formatjs/intl" 8 | import defaultMessages from "../package.nls.json" 9 | const cache = createIntlCache() 10 | 11 | export let intl: ReturnType 12 | 13 | function init() { 14 | let messages: IntlConfig["messages"] 15 | try { 16 | messages = __non_webpack_require__(`../package.nls.${nlsConfig.locale}.json`) 17 | } catch (err) { 18 | messages = defaultMessages 19 | } 20 | intl = createIntl( 21 | { 22 | defaultLocale: "en", 23 | locale: nlsConfig.locale, 24 | messages, 25 | }, 26 | cache, 27 | ) 28 | } 29 | 30 | init() 31 | -------------------------------------------------------------------------------- /src/service/completionResolve.ts: -------------------------------------------------------------------------------- 1 | import vscode from "vscode" 2 | import { getEntryDescription } from "vscode-css-languageservice/lib/esm/languageFacts/entry" 3 | import { defaultLogger as console } from "~/common/logger" 4 | import { CodeKind, createFencedCodeBlock } from "~/common/markdown" 5 | import type { ServiceOptions } from "~/shared" 6 | import { ICompletionItem } from "~/typings/completion" 7 | import { getName, getReferenceLinks } from "./referenceLink" 8 | import type { TailwindLoader } from "./tailwind" 9 | 10 | export default function completionResolve( 11 | item: ICompletionItem, 12 | state: TailwindLoader, 13 | tabSize: number, 14 | options: ServiceOptions, 15 | ): ICompletionItem { 16 | try { 17 | const payload = item.data 18 | if (!payload) return item 19 | if (payload.type === "theme") return item 20 | 21 | const plugin = state.tw.getPlugin(item.label) 22 | let keyword = state.tw.trimPrefix(item.label) 23 | if (plugin) keyword = plugin.getName() 24 | 25 | item = resolve(item, keyword, state, tabSize, options, payload) 26 | 27 | if (options.references && item.documentation) { 28 | if (typeof item.documentation === "object") { 29 | const links = getReferenceLinks(keyword) 30 | if (links.length > 0) { 31 | item.documentation.value += "\n" 32 | item.documentation.value += links.map((ref, i) => `[Reference](${ref.url}) `).join("\n") 33 | } 34 | } 35 | } 36 | return item 37 | } catch (error) { 38 | console.error(error) 39 | return item 40 | } 41 | } 42 | 43 | function resolve( 44 | item: ICompletionItem, 45 | keyword: string, 46 | state: TailwindLoader, 47 | tabSize: number, 48 | options: ServiceOptions, 49 | payload: ICompletionItem["data"], 50 | ): ICompletionItem { 51 | const { type, entry } = payload 52 | if (type === "css") return item 53 | if (type === "cssProp") { 54 | if (entry) { 55 | const markdown = new vscode.MarkdownString() 56 | const desc = getEntryDescription(entry, true) 57 | if (desc) { 58 | markdown.appendMarkdown(desc.value) 59 | item.documentation = markdown 60 | } 61 | } 62 | return item 63 | } 64 | 65 | if (options.references) { 66 | item.detail = getName(keyword) 67 | if (!item.detail) { 68 | if (type === "screen") { 69 | item.detail = getName("screens") 70 | } else if (type === "color") { 71 | item.detail = "Color" 72 | } 73 | } 74 | } 75 | 76 | if (type === "variant" || type === "screen") { 77 | const key = item.label.replace(new RegExp(`${state.separator}$`), "") 78 | const code = state.tw.renderSimpleVariant(key, tabSize) 79 | if (!code) return item 80 | 81 | const fencedCodeBlock = createFencedCodeBlock(code, CodeKind.SCSS) 82 | 83 | const markdown = new vscode.MarkdownString() 84 | markdown.appendMarkdown(fencedCodeBlock) 85 | item.documentation = markdown 86 | 87 | return item 88 | } 89 | 90 | if (type === "color") return item 91 | 92 | if (options.references) { 93 | const postfix = getRemUnit(item.label, options.rootFontSize, state) 94 | if (postfix) { 95 | if ((item.detail?.length ?? 0) <= 20) { 96 | item.detail = item.detail + postfix 97 | } else { 98 | item.detail = postfix 99 | } 100 | } 101 | } 102 | 103 | const code = state.tw.renderClassname({ classname: item.label, rootFontSize: options.rootFontSize, tabSize }) 104 | if (!code) return item 105 | 106 | const fencedCodeBlock = createFencedCodeBlock(code, CodeKind.SCSS) 107 | if (type === "utility") { 108 | const markdown = new vscode.MarkdownString() 109 | markdown.appendMarkdown(fencedCodeBlock) 110 | item.documentation = markdown 111 | } 112 | 113 | return item 114 | } 115 | 116 | function getRemUnit(classname: string, rootFontSize: number, state: TailwindLoader) { 117 | if (rootFontSize <= 0) { 118 | return "" 119 | } 120 | 121 | const decls = state.tw.renderDecls(classname).decls 122 | const reg = /(-?\d[.\d+e]*)rem/ 123 | 124 | for (const [, values] of decls) { 125 | for (let i = 0; i < values.length; i++) { 126 | const match = reg.exec(values[i]) 127 | if (!match) { 128 | continue 129 | } 130 | const [text, n] = match 131 | const val = parseFloat(n) 132 | if (Number.isNaN(val)) { 133 | continue 134 | } 135 | return ` (${text} = ${rootFontSize * val}px)` 136 | } 137 | } 138 | 139 | return "" 140 | } 141 | -------------------------------------------------------------------------------- /src/service/documentColors.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { 3 | colorFromFunction, 4 | colorFromHex, 5 | colorFromIdentifier, 6 | colorFromTransparent, 7 | isColorFunction, 8 | isColorHexValue, 9 | isColorIdentifier, 10 | isColorTransparent, 11 | parse as parseColors, 12 | } from "~/common/color" 13 | import type { ExtractedToken, TextDocument } from "~/common/extractors/types" 14 | import { defaultLogger as console } from "~/common/logger" 15 | import * as parser from "~/common/parser" 16 | import type { ServiceOptions } from "~/shared" 17 | import { TailwindLoader } from "./tailwind" 18 | 19 | export default function documentColors( 20 | tokens: ExtractedToken[], 21 | document: TextDocument, 22 | state: TailwindLoader, 23 | options: ServiceOptions, 24 | ): vscode.ProviderResult { 25 | if (tokens.length === 0) return [] 26 | const colorInformations: vscode.ColorInformation[] = [] 27 | const start = process.hrtime.bigint() 28 | doDocumentColors(tokens) 29 | const end = process.hrtime.bigint() 30 | console.trace(`documentColors (${Number((end - start) / 10n ** 6n)}ms)`) 31 | return colorInformations 32 | 33 | function doDocumentColors(tokens: ExtractedToken[]) { 34 | try { 35 | for (const token of tokens) { 36 | const { kind, start: offset } = token 37 | if (kind === "theme" || kind === "screen") continue 38 | const { items } = parser.spread(token.value, { separator: state.separator }) 39 | for (const { target } of items) { 40 | if ( 41 | (target.type === parser.NodeType.ShortCss || 42 | target.type === parser.NodeType.ArbitraryClassname) && 43 | target.expr 44 | ) { 45 | const expr = target.expr 46 | const colorTokens = parseColors(expr.value) 47 | for (const t of colorTokens) { 48 | if (isColorHexValue(t)) { 49 | const color = colorFromHex(expr.value.slice(...t.range)) 50 | colorInformations.push({ 51 | color, 52 | range: new vscode.Range( 53 | document.positionAt(offset + expr.range[0] + t.range[0]), 54 | document.positionAt(offset + expr.range[0] + t.range[1]), 55 | ), 56 | }) 57 | } else if (isColorIdentifier(t)) { 58 | const color = colorFromIdentifier(expr.value, t) 59 | colorInformations.push({ 60 | color, 61 | range: new vscode.Range( 62 | document.positionAt(offset + expr.range[0] + t.range[0]), 63 | document.positionAt(offset + expr.range[0] + t.range[1]), 64 | ), 65 | }) 66 | } else if (isColorTransparent(t)) { 67 | const color = colorFromTransparent() 68 | colorInformations.push({ 69 | color, 70 | range: new vscode.Range( 71 | document.positionAt(offset + expr.range[0] + t.range[0]), 72 | document.positionAt(offset + expr.range[0] + t.range[1]), 73 | ), 74 | }) 75 | } else if (isColorFunction(t)) { 76 | const color = colorFromFunction(t) 77 | if (color) { 78 | colorInformations.push({ 79 | color, 80 | range: new vscode.Range( 81 | document.positionAt(offset + expr.range[0] + t.range[0]), 82 | document.positionAt(offset + expr.range[0] + t.range[1]), 83 | ), 84 | }) 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } catch (error) { 92 | console.error(error) 93 | console.error("do document colors failed.") 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/service/referenceLink.ts: -------------------------------------------------------------------------------- 1 | import docs from "./docs.yaml" 2 | import references from "./references.yaml" 3 | 4 | export interface Reference { 5 | name: string 6 | url: string 7 | } 8 | 9 | export function getReferenceLinks(keyword: string) { 10 | const value = keyword.replace(":", "") 11 | const originUrl = references[value] 12 | const twinUrl = references["tw." + value] 13 | const links: Reference[] = [] 14 | const last = /[\w-.]+$/ 15 | if (typeof originUrl === "string") { 16 | const match = originUrl.match(last) 17 | links.push({ name: match?.[0] || "", url: originUrl }) 18 | } 19 | if (typeof twinUrl === "string") { 20 | const match = twinUrl.match(last) 21 | if (match) { 22 | if (match[0] !== "variantConfig.js") { 23 | links.push({ name: "twin.macro", url: twinUrl }) 24 | } 25 | } 26 | } 27 | return links 28 | } 29 | 30 | export function getName(keyword: string): string | undefined { 31 | keyword = keyword.replace(":", "") 32 | const originUrl = references[keyword] 33 | const twinUrl = references["tw." + keyword] 34 | const url = originUrl || twinUrl 35 | if (url) { 36 | if (docs[twinUrl]) { 37 | return docs[twinUrl].name 38 | } 39 | if (docs[originUrl]) { 40 | return docs[originUrl].name 41 | } 42 | 43 | const match = /[\w-]+$/.exec(originUrl) 44 | const text = match?.[0] || "" 45 | if (!originUrl || text) { 46 | return originUrl ? `${text}` : "twin.macro" 47 | } 48 | } 49 | return undefined 50 | } 51 | 52 | export function getDescription(keyword: string): string | undefined { 53 | keyword = keyword.replace(":", "") 54 | const originUrl = references[keyword] 55 | const twinUrl = references["tw." + keyword] 56 | const url = originUrl || twinUrl 57 | if (url) { 58 | if (docs[twinUrl]) { 59 | return docs[twinUrl].desc 60 | } 61 | if (docs[originUrl]) { 62 | return docs[originUrl].desc 63 | } 64 | 65 | const match = /[\w-]+$/.exec(originUrl) 66 | const text = match?.[0] || "" 67 | if (!originUrl || text) { 68 | return originUrl ? `${text}` : "twin.macro" 69 | } 70 | } 71 | return undefined 72 | } 73 | -------------------------------------------------------------------------------- /src/service/tailwind/data.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/Microsoft/vscode/issues/32813 2 | export const ColorProps_Foreground = new Set([]) 3 | export const ColorProps_Border = new Set([]) 4 | export const ColorProps_Background = new Set([ 5 | "color", 6 | "outline-color", 7 | "border-color", 8 | "border-top-color", 9 | "border-right-color", 10 | "border-bottom-color", 11 | "border-left-color", 12 | "background-color", 13 | "text-decoration-color", 14 | "accent-color", 15 | "caret-color", 16 | "fill", 17 | "stroke", 18 | "stop-color", 19 | "column-rule-color", 20 | "--tw-ring-color", 21 | "--tw-ring-offset-color", 22 | "--tw-gradient-from", 23 | "--tw-gradient-to", 24 | "--tw-gradient-stops", 25 | "--tw-shadow-color", 26 | ]) 27 | 28 | export const ColorProps = new Set([...ColorProps_Foreground, ...ColorProps_Border, ...ColorProps_Background]) 29 | 30 | export const deprecated = new Map([ 31 | ["overflow-ellipsis", "The utility 'overflow-ellipsis' is now deprecated, replace it with 'text-ellipsis'."], 32 | ["flex-grow", "The utility 'flex-grow' is now deprecated, replace it with 'grow'."], 33 | ["flex-grow-0", "The utility 'flex-grow-0' is now deprecated, replace it with 'grow-0'."], 34 | ["flex-shrink", "The utility 'flex-shrink' is now deprecated, replace it with 'shrink'."], 35 | ["flex-shrink-0", "The utility 'flex-shrink-0' is now deprecated, replace it with 'shrink-0'."], 36 | ["decoration-slice", "The utility 'decoration-slice' is now deprecated, replace it with 'box-decoration-slice'."], 37 | ["decoration-clone", "The utility 'decoration-clone' is now deprecated, replace it with 'box-decoration-clone'."], 38 | ]) 39 | -------------------------------------------------------------------------------- /src/service/tailwind/twin.ts: -------------------------------------------------------------------------------- 1 | export const twinConfig: Tailwind.ConfigJS = { 2 | plugins: [ 3 | // light mode 4 | function ({ config, addVariant }) { 5 | const mode = config("lightMode", "media") 6 | if (mode === "media") { 7 | addVariant("light", "@media (prefers-color-scheme: light)") 8 | } else if (mode === "class") { 9 | addVariant("light", ".light &") 10 | } else if (Array.isArray(mode) && mode[0] === "class" && typeof mode[1] === "string") { 11 | addVariant("light", `${mode[1]} &`) 12 | } 13 | }, 14 | // media query 15 | function ({ addVariant }) { 16 | addVariant("screen", "@media screen") 17 | addVariant("any-pointer-none", "@media (any-pointer: none)") 18 | addVariant("any-pointer-fine", "@media (any-pointer: fine)") 19 | addVariant("any-pointer-coarse", "@media (any-pointer: coarse)") 20 | addVariant("pointer-none", "@media (pointer: none)") 21 | addVariant("pointer-fine", "@media (pointer: fine)") 22 | addVariant("pointer-coarse", "@media (pointer: coarse)") 23 | addVariant("any-hover", "@media (any-hover: hover)") 24 | addVariant("any-hover-none", "@media (any-hover: none)") 25 | addVariant("can-hover", "@media (hover: hover)") 26 | addVariant("cant-hover", "@media (hover: none)") 27 | }, 28 | // custom 29 | function ({ addVariant }) { 30 | addVariant("even-of-type", "&:nth-of-type(even)") 31 | addVariant("odd-of-type", "&:nth-of-type(odd)") 32 | addVariant("link", "&:link") 33 | addVariant("read-write", "&:read-write") 34 | 35 | addVariant("all", "& *") 36 | addVariant("sibling", "& ~ *") 37 | addVariant("svg", "& svg") 38 | addVariant("all-child", "& > *") 39 | 40 | addVariant("hocus", "&:hover, &:focus") 41 | addVariant("group-hocus", ":merge(.group):hover &, :merge(.group):focus &") 42 | addVariant("peer-hocus", ":merge(.peer):hover ~ &, :merge(.peer):focus ~ &") 43 | }, 44 | // not 45 | function ({ addVariant }) { 46 | const data: Array<[string, string | string[]]> = [ 47 | ["first", "&:first-child"], 48 | ["last", "&:last-child"], 49 | ["only", "&:only-child"], 50 | ["first-of-type", "&:first-of-type"], 51 | ["last-of-type", "&:last-of-type"], 52 | ["only-of-type", "&:only-of-type"], 53 | ["checked", "&:checked"], 54 | ["disabled", "&:disabled"], 55 | ] 56 | data.forEach(([key, value]) => { 57 | if (typeof value === "string") value = [value] 58 | addVariant( 59 | `not-${key}`, 60 | value.map(val => `&:not(${val.replaceAll(/&/g, "")})`), 61 | ) 62 | }) 63 | }, 64 | ], 65 | } 66 | -------------------------------------------------------------------------------- /src/shared.ts: -------------------------------------------------------------------------------- 1 | import type { PnpApi } from "@yarnpkg/pnp" 2 | import { URI } from "vscode-uri" 3 | 4 | export const NAME = "Tailwind Twin IntelliSense" 5 | export const SECTION_ID = "tailwindcss" 6 | export const DIAGNOSTICS_ID = "twin" 7 | 8 | export interface Settings { 9 | enabled: boolean 10 | colorDecorators: "inherit" | "on" | "off" 11 | references: boolean 12 | preferVariantWithParentheses: boolean 13 | fallbackDefaultConfig: boolean 14 | diagnostics: { 15 | enabled: boolean 16 | emptyChecking: boolean 17 | } 18 | jsxPropImportChecking: boolean 19 | rootFontSize: number 20 | logLevel: "none" | "error" | "warning" | "info" | "debug" | "trace" 21 | documentColors: boolean 22 | hoverColorHint: "none" | "hex" | "rgb" | "hsl" 23 | otherLanguages: string[] 24 | minimumContrastRatio: number 25 | } 26 | 27 | export interface ColorDecoration { 28 | color?: string 29 | backgroundColor?: string 30 | borderColor?: string 31 | } 32 | 33 | export enum ExtensionMode { 34 | /** 35 | * The extension is installed normally (for example, from the marketplace 36 | * or VSIX) in the editor. 37 | */ 38 | Production = 1, 39 | 40 | /** 41 | * The extension is running from an `--extensionDevelopmentPath` provided 42 | * when launching the editor. 43 | */ 44 | Development = 2, 45 | 46 | /** 47 | * The extension is running from an `--extensionTestsPath` and 48 | * the extension host is running unit tests. 49 | */ 50 | Test = 3, 51 | } 52 | 53 | /** 54 | * Completion item tags are extra annotations that tweak the rendering of a completion 55 | * item. 56 | */ 57 | export enum CompletionItemTag { 58 | /** 59 | * Render a completion as obsolete, usually using a strike-out. 60 | */ 61 | Deprecated = 1, 62 | } 63 | 64 | export interface Environment { 65 | configPath?: URI 66 | workspaceFolder: URI 67 | extensionUri: URI 68 | serverSourceMapUri: URI 69 | extensionMode: ExtensionMode 70 | pnpContext: PnpApi | undefined 71 | } 72 | 73 | export type ServiceOptions = Settings & Environment 74 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "target": "ES2020", 7 | "lib": ["ESNext"], 8 | "types": ["node", "vscode", "webpack-env", "tailwind-types", "@types/node", "@types/jest"], 9 | "rootDir": "..", 10 | "paths": { 11 | "~/*": ["./*"] 12 | } 13 | }, 14 | "include": ["**/*.ts", "**/*.json", "../package.json", "../package.nls*.json"] 15 | } 16 | -------------------------------------------------------------------------------- /src/typings/completion.d.ts: -------------------------------------------------------------------------------- 1 | import type { CompletionItem } from "vscode" 2 | import type { IPropertyData } from "vscode-css-languageservice" 3 | import { URI } from "vscode-uri" 4 | export type CompletionItemPayloadType = "theme" | "screen" | "color" | "utility" | "variant" | "cssProp" | "css" 5 | 6 | export type CompletionItemPayload = { 7 | type: CompletionItemPayloadType 8 | entry?: IPropertyData 9 | uri?: URI 10 | } 11 | 12 | export interface ICompletionItem extends CompletionItem { 13 | label: string 14 | data: CompletionItemPayload 15 | } 16 | -------------------------------------------------------------------------------- /src/typings/culori.d.ts: -------------------------------------------------------------------------------- 1 | declare module "culori" { 2 | interface RgbSpace { 3 | mode: "rgb" 4 | r: number 5 | g: number 6 | b: number 7 | alpha: number 8 | } 9 | 10 | interface HslSpace { 11 | mode: "hsl" 12 | h: number 13 | s: number 14 | b: number 15 | alpha: number 16 | } 17 | 18 | interface LabSpace { 19 | mode: "lab" 20 | l: number 21 | a: number 22 | b: number 23 | } 24 | 25 | type Color = RgbSpace | HslSpace 26 | 27 | export function converter(mode: "rgb"): (value: string | Color) => RgbSpace 28 | export function converter(mode: "hsl"): (value: string | Color) => HslSpace 29 | export function converter(mode: "lab"): (value: string | Color) => LabSpace 30 | 31 | export function parse(value: string): Color | undefined 32 | 33 | export function serializeHex(color: RgbSpace): string 34 | export function serializeHex8(color: RgbSpace): string 35 | export function formatHex(color: string | Color): string 36 | export function formatHex8(color: string | Color): string 37 | 38 | export function serializeRgb(color: RgbSpace): string 39 | export function formatRgb(color: string | Color): string 40 | 41 | export function serializeHsl(color: HslSpace): string 42 | export function formatHsl(color: string | Color): string 43 | } 44 | -------------------------------------------------------------------------------- /src/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare var __COMMIT_HASH__: string 2 | -------------------------------------------------------------------------------- /src/typings/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.yaml" { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | const data: any 4 | export default data 5 | } 6 | 7 | declare module "*.yml" { 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | const data: any 10 | export default data 11 | } 12 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/VERSION.md: -------------------------------------------------------------------------------- 1 | 6.0.1 2 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/data/webCustomData.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/data/webCustomData" { 2 | import { CSSDataV1 } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | export const cssData: CSSDataV1 4 | } 5 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/builtinData.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/builtinData" { 2 | export const positionKeywords: { 3 | [name: string]: string 4 | } 5 | export const repeatStyleKeywords: { 6 | [name: string]: string 7 | } 8 | export const lineStyleKeywords: { 9 | [name: string]: string 10 | } 11 | export const lineWidthKeywords: string[] 12 | export const boxKeywords: { 13 | [name: string]: string 14 | } 15 | export const geometryBoxKeywords: { 16 | [name: string]: string 17 | } 18 | export const cssWideKeywords: { 19 | [name: string]: string 20 | } 21 | export const cssWideFunctions: { 22 | [name: string]: string 23 | } 24 | export const imageFunctions: { 25 | [name: string]: string 26 | } 27 | export const transitionTimingFunctions: { 28 | [name: string]: string 29 | } 30 | export const basicShapeFunctions: { 31 | [name: string]: string 32 | } 33 | export const units: { 34 | [unitName: string]: string[] 35 | } 36 | export const html5Tags: string[] 37 | export const svgElements: string[] 38 | export const pageBoxDirectives: string[] 39 | } 40 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/colors.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/colors" { 2 | import { Color } from "vscode-css-languageservice/lib/esm/cssLanguageService" 3 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 4 | export const colorFunctions: { 5 | func: string 6 | desc: string 7 | }[] 8 | export const colors: { 9 | [name: string]: string 10 | } 11 | export const colorKeywords: { 12 | [name: string]: string 13 | } 14 | export function isColorConstructor(node: nodes.Function): boolean 15 | /** 16 | * Returns true if the node is a color value - either 17 | * defined a hex number, as rgb or rgba function, or 18 | * as color name. 19 | */ 20 | export function isColorValue(node: nodes.Node): boolean 21 | export function hexDigit(charCode: number): number 22 | export function colorFromHex(text: string): Color | null 23 | export function colorFrom256RGB(red: number, green: number, blue: number, alpha?: number): Color 24 | export function colorFromHSL(hue: number, sat: number, light: number, alpha?: number): Color 25 | export interface HSLA { 26 | h: number 27 | s: number 28 | l: number 29 | a: number 30 | } 31 | export function hslFromColor(rgba: Color): HSLA 32 | export function getColorValue(node: nodes.Node): Color | null 33 | } 34 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/dataManager.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/dataManager" { 2 | import { 3 | IAtDirectiveData, 4 | ICSSDataProvider, 5 | IPropertyData, 6 | IPseudoClassData, 7 | IPseudoElementData, 8 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 9 | export class CSSDataManager { 10 | private dataProviders 11 | private _propertySet 12 | private _atDirectiveSet 13 | private _pseudoClassSet 14 | private _pseudoElementSet 15 | private _properties 16 | private _atDirectives 17 | private _pseudoClasses 18 | private _pseudoElements 19 | constructor(options?: { useDefaultDataProvider?: boolean; customDataProviders?: ICSSDataProvider[] }) 20 | setDataProviders(builtIn: boolean, providers: ICSSDataProvider[]): void 21 | /** 22 | * Collect all data & handle duplicates 23 | */ 24 | private collectData 25 | getProperty(name: string): IPropertyData | undefined 26 | getAtDirective(name: string): IAtDirectiveData | undefined 27 | getPseudoClass(name: string): IPseudoClassData | undefined 28 | getPseudoElement(name: string): IPseudoElementData | undefined 29 | getProperties(): IPropertyData[] 30 | getAtDirectives(): IAtDirectiveData[] 31 | getPseudoClasses(): IPseudoClassData[] 32 | getPseudoElements(): IPseudoElementData[] 33 | isKnownProperty(name: string): boolean 34 | isStandardProperty(name: string): boolean 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/dataProvider.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/dataProvider" { 2 | import { 3 | CSSDataV1, 4 | IAtDirectiveData, 5 | ICSSDataProvider, 6 | IPropertyData, 7 | IPseudoClassData, 8 | IPseudoElementData, 9 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 10 | export class CSSDataProvider implements ICSSDataProvider { 11 | private _properties 12 | private _atDirectives 13 | private _pseudoClasses 14 | private _pseudoElements 15 | /** 16 | * Currently, unversioned data uses the V1 implementation 17 | * In the future when the provider handles multiple versions of HTML custom data, 18 | * use the latest implementation for unversioned data 19 | */ 20 | constructor(data: CSSDataV1) 21 | provideProperties(): IPropertyData[] 22 | provideAtDirectives(): IAtDirectiveData[] 23 | providePseudoClasses(): IPseudoClassData[] 24 | providePseudoElements(): IPseudoElementData[] 25 | private addData 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/entry.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/entry" { 2 | import { 3 | EntryStatus, 4 | HoverSettings, 5 | IAtDirectiveData, 6 | IPropertyData, 7 | IPseudoClassData, 8 | IPseudoElementData, 9 | IValueData, 10 | MarkedString, 11 | MarkupContent, 12 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 13 | export interface Browsers { 14 | E?: string 15 | FF?: string 16 | IE?: string 17 | O?: string 18 | C?: string 19 | S?: string 20 | count: number 21 | all: boolean 22 | onCodeComplete: boolean 23 | } 24 | export const browserNames: { 25 | E: string 26 | FF: string 27 | S: string 28 | C: string 29 | IE: string 30 | O: string 31 | } 32 | export function getEntryDescription( 33 | entry: IEntry2, 34 | doesSupportMarkdown: boolean, 35 | settings?: HoverSettings, 36 | ): MarkupContent | undefined 37 | export function textToMarkedString(text: string): MarkedString 38 | /** 39 | * Input is like `["E12","FF49","C47","IE","O"]` 40 | * Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera` 41 | */ 42 | export function getBrowserLabel(browsers?: string[]): string | null 43 | export type IEntry2 = IPropertyData | IAtDirectiveData | IPseudoClassData | IPseudoElementData | IValueData 44 | /** 45 | * Todo@Pine: Drop these two types and use IEntry2 46 | */ 47 | export interface IEntry { 48 | name: string 49 | description?: string | MarkupContent 50 | browsers?: string[] 51 | restrictions?: string[] 52 | status?: EntryStatus 53 | syntax?: string 54 | values?: IValue[] 55 | } 56 | export interface IValue { 57 | name: string 58 | description?: string | MarkupContent 59 | browsers?: string[] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/languageFacts/facts.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/languageFacts/facts" { 2 | export * from "vscode-css-languageservice/lib/esm/languageFacts/builtinData" 3 | export * from "vscode-css-languageservice/lib/esm/languageFacts/colors" 4 | export * from "vscode-css-languageservice/lib/esm/languageFacts/entry" 5 | } 6 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/cssErrors.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/cssErrors" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | export class CSSIssueType implements nodes.IRule { 4 | id: string 5 | message: string 6 | constructor(id: string, message: string) 7 | } 8 | export const ParseError: { 9 | NumberExpected: CSSIssueType 10 | ConditionExpected: CSSIssueType 11 | RuleOrSelectorExpected: CSSIssueType 12 | DotExpected: CSSIssueType 13 | ColonExpected: CSSIssueType 14 | SemiColonExpected: CSSIssueType 15 | TermExpected: CSSIssueType 16 | ExpressionExpected: CSSIssueType 17 | OperatorExpected: CSSIssueType 18 | IdentifierExpected: CSSIssueType 19 | PercentageExpected: CSSIssueType 20 | URIOrStringExpected: CSSIssueType 21 | URIExpected: CSSIssueType 22 | VariableNameExpected: CSSIssueType 23 | VariableValueExpected: CSSIssueType 24 | PropertyValueExpected: CSSIssueType 25 | LeftCurlyExpected: CSSIssueType 26 | RightCurlyExpected: CSSIssueType 27 | LeftSquareBracketExpected: CSSIssueType 28 | RightSquareBracketExpected: CSSIssueType 29 | LeftParenthesisExpected: CSSIssueType 30 | RightParenthesisExpected: CSSIssueType 31 | CommaExpected: CSSIssueType 32 | PageDirectiveOrDeclarationExpected: CSSIssueType 33 | UnknownAtRule: CSSIssueType 34 | UnknownKeyword: CSSIssueType 35 | SelectorExpected: CSSIssueType 36 | StringLiteralExpected: CSSIssueType 37 | WhitespaceExpected: CSSIssueType 38 | MediaQueryExpected: CSSIssueType 39 | IdentifierOrWildcardExpected: CSSIssueType 40 | WildcardExpected: CSSIssueType 41 | IdentifierOrVariableExpected: CSSIssueType 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/cssParser.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/cssParser" { 2 | import { TextDocument } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import { CSSIssueType } from "vscode-css-languageservice/lib/esm/parser/cssErrors" 4 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 5 | import { IToken, Scanner, TokenType } from "vscode-css-languageservice/lib/esm/parser/cssScanner" 6 | 7 | export interface IMark { 8 | prev?: IToken 9 | curr: IToken 10 | pos: number 11 | } 12 | export class Parser { 13 | scanner: Scanner 14 | token: IToken 15 | prevToken?: IToken 16 | private lastErrorToken? 17 | constructor(scnr?: Scanner) 18 | peekIdent(text: string): boolean 19 | peekKeyword(text: string): boolean 20 | peekDelim(text: string): boolean 21 | peek(type: TokenType): boolean 22 | peekOne(...types: TokenType[]): boolean 23 | peekRegExp(type: TokenType, regEx: RegExp): boolean 24 | hasWhitespace(): boolean 25 | consumeToken(): void 26 | mark(): IMark 27 | restoreAtMark(mark: IMark): void 28 | try(func: () => nodes.Node | null): nodes.Node | null 29 | acceptOneKeyword(keywords: string[]): boolean 30 | accept(type: TokenType): boolean 31 | acceptIdent(text: string): boolean 32 | acceptKeyword(text: string): boolean 33 | acceptDelim(text: string): boolean 34 | acceptRegexp(regEx: RegExp): boolean 35 | _parseRegexp(regEx: RegExp): nodes.Node 36 | protected acceptUnquotedString(): boolean 37 | resync(resyncTokens: TokenType[] | undefined, resyncStopTokens: TokenType[] | undefined): boolean 38 | createNode(nodeType: nodes.NodeType): nodes.Node 39 | create(ctor: nodes.NodeConstructor): T 40 | finish( 41 | node: T, 42 | error?: CSSIssueType, 43 | resyncTokens?: TokenType[], 44 | resyncStopTokens?: TokenType[], 45 | ): T 46 | markError( 47 | node: T, 48 | error: CSSIssueType, 49 | resyncTokens?: TokenType[], 50 | resyncStopTokens?: TokenType[], 51 | ): void 52 | parseStylesheet(textDocument: TextDocument): nodes.Stylesheet 53 | internalParse( 54 | input: string, 55 | parseFunc: () => U, 56 | textProvider?: nodes.ITextProvider, 57 | ): U 58 | _parseStylesheet(): nodes.Stylesheet 59 | _parseStylesheetStart(): nodes.Node | null 60 | _parseStylesheetStatement(isNested?: boolean): nodes.Node | null 61 | _parseStylesheetAtStatement(isNested?: boolean): nodes.Node | null 62 | _tryParseRuleset(isNested: boolean): nodes.RuleSet | null 63 | _parseRuleset(isNested?: boolean): nodes.RuleSet | null 64 | protected _parseRuleSetDeclarationAtStatement(): nodes.Node | null 65 | _parseRuleSetDeclaration(): nodes.Node | null 66 | _needsSemicolonAfter(node: nodes.Node): boolean 67 | _parseDeclarations(parseDeclaration: () => nodes.Node | null): nodes.Declarations | null 68 | _parseBody(node: T, parseDeclaration: () => nodes.Node | null): T 69 | _parseSelector(isNested: boolean): nodes.Selector | null 70 | _parseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null 71 | _tryParseCustomPropertyDeclaration(stopTokens?: TokenType[]): nodes.CustomPropertyDeclaration | null 72 | /** 73 | * Parse custom property values. 74 | * 75 | * Based on https://www.w3.org/TR/css-variables/#syntax 76 | * 77 | * This code is somewhat unusual, as the allowed syntax is incredibly broad, 78 | * parsing almost any sequence of tokens, save for a small set of exceptions. 79 | * Unbalanced delimitors, invalid tokens, and declaration 80 | * terminators like semicolons and !important directives (when not inside 81 | * of delimitors). 82 | */ 83 | _parseCustomPropertyValue(stopTokens?: TokenType[]): nodes.Node 84 | _tryToParseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null 85 | _parseProperty(): nodes.Property | null 86 | _parsePropertyIdentifier(): nodes.Identifier | null 87 | _parseCharset(): nodes.Node | null 88 | _parseImport(): nodes.Node | null 89 | _parseNamespace(): nodes.Node | null 90 | _parseFontFace(): nodes.Node | null 91 | _parseViewPort(): nodes.Node | null 92 | private keyframeRegex 93 | _parseKeyframe(): nodes.Node | null 94 | _parseKeyframeIdent(): nodes.Node | null 95 | _parseKeyframeSelector(): nodes.Node | null 96 | _tryParseKeyframeSelector(): nodes.Node | null 97 | _parseSupports(isNested?: boolean): nodes.Node | null 98 | _parseSupportsDeclaration(isNested?: boolean): nodes.Node | null 99 | protected _parseSupportsCondition(): nodes.Node 100 | private _parseSupportsConditionInParens 101 | _parseMediaDeclaration(isNested?: boolean): nodes.Node | null 102 | _parseMedia(isNested?: boolean): nodes.Node | null 103 | _parseMediaQueryList(): nodes.Medialist 104 | _parseMediaQuery(resyncStopToken: TokenType[]): nodes.Node | null 105 | _parseMediaContentStart(): nodes.Node | null 106 | _parseMediaFeatureName(): nodes.Node | null 107 | _parseMedium(): nodes.Node | null 108 | _parsePageDeclaration(): nodes.Node | null 109 | _parsePage(): nodes.Node | null 110 | _parsePageMarginBox(): nodes.Node | null 111 | _parsePageSelector(): nodes.Node | null 112 | _parseDocument(): nodes.Node | null 113 | _parseUnknownAtRule(): nodes.Node | null 114 | _parseUnknownAtRuleName(): nodes.Node 115 | _parseOperator(): nodes.Operator | null 116 | _parseUnaryOperator(): nodes.Node | null 117 | _parseCombinator(): nodes.Node | null 118 | _parseSimpleSelector(): nodes.SimpleSelector | null 119 | _parseSimpleSelectorBody(): nodes.Node | null 120 | _parseSelectorIdent(): nodes.Node | null 121 | _parseHash(): nodes.Node | null 122 | _parseClass(): nodes.Node | null 123 | _parseElementName(): nodes.Node | null 124 | _parseNamespacePrefix(): nodes.Node | null 125 | _parseAttrib(): nodes.Node | null 126 | _parsePseudo(): nodes.Node | null 127 | _tryParsePseudoIdentifier(): nodes.Node | null 128 | _tryParsePrio(): nodes.Node | null 129 | _parsePrio(): nodes.Node | null 130 | _parseExpr(stopOnComma?: boolean): nodes.Expression | null 131 | _parseNamedLine(): nodes.Node | null 132 | _parseBinaryExpr( 133 | preparsedLeft?: nodes.BinaryExpression, 134 | preparsedOper?: nodes.Node, 135 | ): nodes.BinaryExpression | null 136 | _parseTerm(): nodes.Term | null 137 | _parseTermExpression(): nodes.Node | null 138 | _parseOperation(): nodes.Node | null 139 | _parseNumeric(): nodes.NumericValue | null 140 | _parseStringLiteral(): nodes.Node | null 141 | _parseURILiteral(): nodes.Node | null 142 | _parseURLArgument(): nodes.Node | null 143 | _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null 144 | _parseFunction(): nodes.Function | null 145 | _parseFunctionIdentifier(): nodes.Identifier | null 146 | _parseFunctionArgument(): nodes.Node | null 147 | _parseHexColor(): nodes.Node | null 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/cssScanner.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/cssScanner" { 2 | export enum TokenType { 3 | Ident = 0, 4 | AtKeyword = 1, 5 | String = 2, 6 | BadString = 3, 7 | UnquotedString = 4, 8 | Hash = 5, 9 | Num = 6, 10 | Percentage = 7, 11 | Dimension = 8, 12 | UnicodeRange = 9, 13 | CDO = 10, 14 | CDC = 11, 15 | Colon = 12, 16 | SemiColon = 13, 17 | CurlyL = 14, 18 | CurlyR = 15, 19 | ParenthesisL = 16, 20 | ParenthesisR = 17, 21 | BracketL = 18, 22 | BracketR = 19, 23 | Whitespace = 20, 24 | Includes = 21, 25 | Dashmatch = 22, 26 | SubstringOperator = 23, 27 | PrefixOperator = 24, 28 | SuffixOperator = 25, 29 | Delim = 26, 30 | EMS = 27, 31 | EXS = 28, 32 | Length = 29, 33 | Angle = 30, 34 | Time = 31, 35 | Freq = 32, 36 | Exclamation = 33, 37 | Resolution = 34, 38 | Comma = 35, 39 | Charset = 36, 40 | EscapedJavaScript = 37, 41 | BadEscapedJavaScript = 38, 42 | Comment = 39, 43 | SingleLineComment = 40, 44 | EOF = 41, 45 | CustomToken = 42, 46 | } 47 | export interface IToken { 48 | type: TokenType 49 | text: string 50 | offset: number 51 | len: number 52 | } 53 | export class MultiLineStream { 54 | private source 55 | private len 56 | private position 57 | constructor(source: string) 58 | substring(from: number, to?: number): string 59 | eos(): boolean 60 | pos(): number 61 | goBackTo(pos: number): void 62 | goBack(n: number): void 63 | advance(n: number): void 64 | nextChar(): number 65 | peekChar(n?: number): number 66 | lookbackChar(n?: number): number 67 | advanceIfChar(ch: number): boolean 68 | advanceIfChars(ch: number[]): boolean 69 | advanceWhileChar(condition: (ch: number) => boolean): number 70 | } 71 | export class Scanner { 72 | stream: MultiLineStream 73 | ignoreComment: boolean 74 | ignoreWhitespace: boolean 75 | inURL: boolean 76 | setSource(input: string): void 77 | finishToken(offset: number, type: TokenType, text?: string): IToken 78 | substring(offset: number, len: number): string 79 | pos(): number 80 | goBackTo(pos: number): void 81 | scanUnquotedString(): IToken | null 82 | scan(): IToken 83 | protected scanNext(offset: number): IToken 84 | protected trivia(): IToken | null 85 | protected comment(): boolean 86 | private _number 87 | private _newline 88 | private _escape 89 | private _stringChar 90 | private _string 91 | private _unquotedChar 92 | protected _unquotedString(result: string[]): boolean 93 | private _whitespace 94 | private _name 95 | protected ident(result: string[]): boolean 96 | private _identFirstChar 97 | private _minus 98 | private _identChar 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/cssSymbolScope.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/cssSymbolScope" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | export class Scope { 4 | parent: Scope | null 5 | children: Scope[] 6 | offset: number 7 | length: number 8 | private symbols 9 | constructor(offset: number, length: number) 10 | addChild(scope: Scope): void 11 | setParent(scope: Scope): void 12 | findScope(offset: number, length?: number): Scope | null 13 | private findInScope 14 | addSymbol(symbol: Symbol): void 15 | getSymbol(name: string, type: nodes.ReferenceType): Symbol | null 16 | getSymbols(): Symbol[] 17 | } 18 | export class GlobalScope extends Scope { 19 | constructor() 20 | } 21 | export class Symbol { 22 | name: string 23 | value: string | undefined 24 | type: nodes.ReferenceType 25 | node: nodes.Node 26 | constructor(name: string, value: string | undefined, node: nodes.Node, type: nodes.ReferenceType) 27 | } 28 | export class ScopeBuilder implements nodes.IVisitor { 29 | scope: Scope 30 | constructor(scope: Scope) 31 | private addSymbol 32 | private addScope 33 | private addSymbolToChildScope 34 | visitNode(node: nodes.Node): boolean 35 | visitRuleSet(node: nodes.RuleSet): boolean 36 | visitVariableDeclarationNode(node: nodes.VariableDeclaration): boolean 37 | visitFunctionParameterNode(node: nodes.FunctionParameter): boolean 38 | visitCustomPropertyDeclarationNode(node: nodes.CustomPropertyDeclaration): boolean 39 | private addCSSVariable 40 | } 41 | export class Symbols { 42 | private global 43 | constructor(node: nodes.Node) 44 | findSymbolsAtOffset(offset: number, referenceType: nodes.ReferenceType): Symbol[] 45 | private internalFindSymbol 46 | private evaluateReferenceTypes 47 | findSymbolFromNode(node: nodes.Node): Symbol | null 48 | matchesSymbol(node: nodes.Node, symbol: Symbol): boolean 49 | findSymbol(name: string, type: nodes.ReferenceType, offset: number): Symbol | null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/lessParser.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/lessParser" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | import * as cssParser from "vscode-css-languageservice/lib/esm/parser/cssParser" 4 | import { TokenType } from "vscode-css-languageservice/lib/esm/parser/cssScanner" 5 | export class LESSParser extends cssParser.Parser { 6 | constructor() 7 | _parseStylesheetStatement(isNested?: boolean): nodes.Node | null 8 | _parseImport(): nodes.Node | null 9 | _parsePlugin(): nodes.Node | null 10 | _parseMediaQuery(resyncStopToken: TokenType[]): nodes.Node | null 11 | _parseMediaDeclaration(isNested?: boolean): nodes.Node | null 12 | _parseMediaFeatureName(): nodes.Node | null 13 | _parseVariableDeclaration(panic?: TokenType[]): nodes.VariableDeclaration | null 14 | _parseDetachedRuleSet(): nodes.Node | null 15 | _parseDetachedRuleSetBody(): nodes.Node | null 16 | _addLookupChildren(node: nodes.Node): boolean 17 | _parseLookupValue(): nodes.Node | null 18 | _parseVariable(declaration?: boolean, insideLookup?: boolean): nodes.Variable | null 19 | _parseTermExpression(): nodes.Node | null 20 | _parseEscaped(): nodes.Node | null 21 | _parseOperator(): nodes.Node | null 22 | _parseGuardOperator(): nodes.Node | null 23 | _parseRuleSetDeclaration(): nodes.Node | null 24 | _parseKeyframeIdent(): nodes.Node | null 25 | _parseKeyframeSelector(): nodes.Node | null 26 | _parseSimpleSelectorBody(): nodes.Node | null 27 | _parseSelector(isNested: boolean): nodes.Selector | null 28 | _parseSelectorCombinator(): nodes.Node | null 29 | _parseSelectorIdent(): nodes.Node | null 30 | _parsePropertyIdentifier(inLookup?: boolean): nodes.Identifier | null 31 | private peekInterpolatedIdent 32 | _acceptInterpolatedIdent(node: nodes.Node, identRegex?: RegExp): boolean 33 | _parseInterpolation(): nodes.Node | null 34 | _tryParseMixinDeclaration(): nodes.Node | null 35 | private _parseMixInBodyDeclaration 36 | private _parseMixinDeclarationIdentifier 37 | _parsePseudo(): nodes.Node | null 38 | _parseExtend(): nodes.Node | null 39 | private _completeExtends 40 | _parseDetachedRuleSetMixin(): nodes.Node | null 41 | _tryParseMixinReference(atRoot?: boolean): nodes.Node | null 42 | _parseMixinArgument(): nodes.Node | null 43 | _parseMixinParameter(): nodes.Node | null 44 | _parseGuard(): nodes.LessGuard | null 45 | _parseGuardCondition(): nodes.Node | null 46 | _parseFunction(): nodes.Function | null 47 | _parseFunctionIdentifier(): nodes.Identifier | null 48 | _parseURLArgument(): nodes.Node | null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/lessScanner.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/lessScanner" { 2 | import * as scanner from "vscode-css-languageservice/lib/esm/parser/cssScanner" 3 | export const Ellipsis: scanner.TokenType 4 | export class LESSScanner extends scanner.Scanner { 5 | protected scanNext(offset: number): scanner.IToken 6 | protected comment(): boolean 7 | private escapedJavaScript 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/scssErrors.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/scssErrors" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | export class SCSSIssueType implements nodes.IRule { 4 | id: string 5 | message: string 6 | constructor(id: string, message: string) 7 | } 8 | export const SCSSParseError: { 9 | FromExpected: SCSSIssueType 10 | ThroughOrToExpected: SCSSIssueType 11 | InExpected: SCSSIssueType 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/scssParser.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/scssParser" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | import * as cssParser from "vscode-css-languageservice/lib/esm/parser/cssParser" 4 | import { TokenType } from "vscode-css-languageservice/lib/esm/parser/cssScanner" 5 | export class SCSSParser extends cssParser.Parser { 6 | constructor() 7 | _parseStylesheetStatement(isNested?: boolean): nodes.Node | null 8 | _parseImport(): nodes.Node | null 9 | _parseVariableDeclaration(panic?: TokenType[]): nodes.VariableDeclaration | null 10 | _parseMediaContentStart(): nodes.Node | null 11 | _parseMediaFeatureName(): nodes.Node | null 12 | _parseKeyframeSelector(): nodes.Node | null 13 | _parseVariable(): nodes.Variable | null 14 | _parseModuleMember(): nodes.Module | null 15 | _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null 16 | _parseTermExpression(): nodes.Node | null 17 | _parseInterpolation(): nodes.Node | null 18 | _parseOperator(): nodes.Node | null 19 | _parseUnaryOperator(): nodes.Node | null 20 | _parseRuleSetDeclaration(): nodes.Node | null 21 | _parseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null 22 | _parseNestedProperties(): nodes.NestedProperties 23 | _parseExtends(): nodes.Node | null 24 | _parseSimpleSelectorBody(): nodes.Node | null 25 | _parseSelectorCombinator(): nodes.Node | null 26 | _parseSelectorPlaceholder(): nodes.Node | null 27 | _parseElementName(): nodes.Node | null 28 | _tryParsePseudoIdentifier(): nodes.Node | null 29 | _parseWarnAndDebug(): nodes.Node | null 30 | _parseControlStatement(parseStatement?: () => nodes.Node | null): nodes.Node | null 31 | _parseIfStatement(parseStatement: () => nodes.Node | null): nodes.Node | null 32 | private _internalParseIfStatement 33 | _parseForStatement(parseStatement: () => nodes.Node | null): nodes.Node | null 34 | _parseEachStatement(parseStatement: () => nodes.Node | null): nodes.Node | null 35 | _parseWhileStatement(parseStatement: () => nodes.Node | null): nodes.Node | null 36 | _parseFunctionBodyDeclaration(): nodes.Node | null 37 | _parseFunctionDeclaration(): nodes.Node | null 38 | _parseReturnStatement(): nodes.Node | null 39 | _parseMixinDeclaration(): nodes.Node | null 40 | _parseParameterDeclaration(): nodes.Node | null 41 | _parseMixinContent(): nodes.Node | null 42 | _parseMixinReference(): nodes.Node | null 43 | _parseMixinContentDeclaration(): nodes.MixinContentDeclaration 44 | _parseMixinReferenceBodyStatement(): nodes.Node | null 45 | _parseFunctionArgument(): nodes.Node | null 46 | _parseURLArgument(): nodes.Node | null 47 | _parseOperation(): nodes.Node | null 48 | _parseListElement(): nodes.Node | null 49 | _parseUse(): nodes.Node | null 50 | _parseModuleConfigDeclaration(): nodes.Node | null 51 | _parseForward(): nodes.Node | null 52 | _parseForwardVisibility(): nodes.Node | null 53 | protected _parseSupportsCondition(): nodes.Node 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/parser/scssScanner.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/parser/scssScanner" { 2 | import { IToken, Scanner, TokenType } from "vscode-css-languageservice/lib/esm/parser/cssScanner" 3 | export const VariableName: number 4 | export const InterpolationFunction: TokenType 5 | export const Default: TokenType 6 | export const EqualsOperator: TokenType 7 | export const NotEqualsOperator: TokenType 8 | export const GreaterEqualsOperator: TokenType 9 | export const SmallerEqualsOperator: TokenType 10 | export const Ellipsis: TokenType 11 | export const Module: TokenType 12 | export class SCSSScanner extends Scanner { 13 | protected scanNext(offset: number): IToken 14 | protected comment(): boolean 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssCodeActions.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssCodeActions" { 2 | import { 3 | CodeAction, 4 | CodeActionContext, 5 | Command, 6 | Range, 7 | TextDocument, 8 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 9 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 10 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 11 | export class CSSCodeActions { 12 | private readonly cssDataManager 13 | constructor(cssDataManager: CSSDataManager) 14 | doCodeActions( 15 | document: TextDocument, 16 | range: Range, 17 | context: CodeActionContext, 18 | stylesheet: nodes.Stylesheet, 19 | ): Command[] 20 | doCodeActions2( 21 | document: TextDocument, 22 | range: Range, 23 | context: CodeActionContext, 24 | stylesheet: nodes.Stylesheet, 25 | ): CodeAction[] 26 | private getFixesForUnknownProperty 27 | private appendFixesForMarker 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssCompletion.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssCompletion" { 2 | import { 3 | CompletionList, 4 | CompletionSettings, 5 | DocumentContext, 6 | ICompletionParticipant, 7 | IPropertyData, 8 | LanguageServiceOptions, 9 | Position, 10 | Range, 11 | TextDocument, 12 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 13 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 14 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 15 | import { Symbols } from "vscode-css-languageservice/lib/esm/parser/cssSymbolScope" 16 | export class CSSCompletion { 17 | variablePrefix: string | null 18 | private lsOptions 19 | private cssDataManager 20 | private defaultSettings? 21 | private supportsMarkdown 22 | position: Position 23 | offset: number 24 | currentWord: string 25 | textDocument: TextDocument 26 | styleSheet: nodes.Stylesheet 27 | symbolContext: Symbols 28 | defaultReplaceRange: Range 29 | nodePath: nodes.Node[] 30 | completionParticipants: ICompletionParticipant[] 31 | documentSettings?: CompletionSettings 32 | constructor(variablePrefix: string | null, lsOptions: LanguageServiceOptions, cssDataManager: CSSDataManager) 33 | configure(settings?: CompletionSettings): void 34 | protected getSymbolContext(): Symbols 35 | setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void 36 | doComplete2( 37 | document: TextDocument, 38 | position: Position, 39 | styleSheet: nodes.Stylesheet, 40 | documentContext: DocumentContext, 41 | completionSettings?: CompletionSettings | undefined, 42 | ): Promise 43 | doComplete( 44 | document: TextDocument, 45 | position: Position, 46 | styleSheet: nodes.Stylesheet, 47 | documentSettings: CompletionSettings | undefined, 48 | ): CompletionList 49 | protected isImportPathParent(type: nodes.NodeType): boolean 50 | private finalize 51 | private findInNodePath 52 | getCompletionsForDeclarationProperty( 53 | declaration: nodes.Declaration | null, 54 | result: CompletionList, 55 | ): CompletionList 56 | private getPropertyProposals 57 | private get isTriggerPropertyValueCompletionEnabled() 58 | private get isCompletePropertyWithSemicolonEnabled() 59 | getCompletionsForDeclarationValue(node: nodes.Declaration, result: CompletionList): CompletionList 60 | getValueEnumProposals( 61 | entry: IPropertyData, 62 | existingNode: nodes.Node | null, 63 | result: CompletionList, 64 | ): CompletionList 65 | getCSSWideKeywordProposals( 66 | entry: IPropertyData, 67 | existingNode: nodes.Node | null, 68 | result: CompletionList, 69 | ): CompletionList 70 | getCompletionsForInterpolation(node: nodes.Interpolation, result: CompletionList): CompletionList 71 | getVariableProposals(existingNode: nodes.Node | null, result: CompletionList): CompletionList 72 | getVariableProposalsForCSSVarFunction(result: CompletionList): CompletionList 73 | getUnitProposals(entry: IPropertyData, existingNode: nodes.Node | null, result: CompletionList): CompletionList 74 | protected getCompletionRange(existingNode: nodes.Node | null): Range 75 | protected getColorProposals( 76 | entry: IPropertyData, 77 | existingNode: nodes.Node | null, 78 | result: CompletionList, 79 | ): CompletionList 80 | protected getPositionProposals( 81 | entry: IPropertyData, 82 | existingNode: nodes.Node | null, 83 | result: CompletionList, 84 | ): CompletionList 85 | protected getRepeatStyleProposals( 86 | entry: IPropertyData, 87 | existingNode: nodes.Node | null, 88 | result: CompletionList, 89 | ): CompletionList 90 | protected getLineStyleProposals( 91 | entry: IPropertyData, 92 | existingNode: nodes.Node | null, 93 | result: CompletionList, 94 | ): CompletionList 95 | protected getLineWidthProposals( 96 | entry: IPropertyData, 97 | existingNode: nodes.Node | null, 98 | result: CompletionList, 99 | ): CompletionList 100 | protected getGeometryBoxProposals( 101 | entry: IPropertyData, 102 | existingNode: nodes.Node | null, 103 | result: CompletionList, 104 | ): CompletionList 105 | protected getBoxProposals( 106 | entry: IPropertyData, 107 | existingNode: nodes.Node | null, 108 | result: CompletionList, 109 | ): CompletionList 110 | protected getImageProposals( 111 | entry: IPropertyData, 112 | existingNode: nodes.Node | null, 113 | result: CompletionList, 114 | ): CompletionList 115 | protected getTimingFunctionProposals( 116 | entry: IPropertyData, 117 | existingNode: nodes.Node | null, 118 | result: CompletionList, 119 | ): CompletionList 120 | protected getBasicShapeProposals( 121 | entry: IPropertyData, 122 | existingNode: nodes.Node | null, 123 | result: CompletionList, 124 | ): CompletionList 125 | getCompletionsForStylesheet(result: CompletionList): CompletionList 126 | getCompletionForTopLevel(result: CompletionList): CompletionList 127 | getCompletionsForRuleSet(ruleSet: nodes.RuleSet, result: CompletionList): CompletionList 128 | getCompletionsForSelector( 129 | ruleSet: nodes.RuleSet | null, 130 | isNested: boolean, 131 | result: CompletionList, 132 | ): CompletionList 133 | getCompletionsForDeclarations( 134 | declarations: nodes.Declarations | null | undefined, 135 | result: CompletionList, 136 | ): CompletionList 137 | getCompletionsForVariableDeclaration( 138 | declaration: nodes.VariableDeclaration, 139 | result: CompletionList, 140 | ): CompletionList 141 | getCompletionsForExpression(expression: nodes.Expression, result: CompletionList): CompletionList 142 | getCompletionsForFunctionArgument( 143 | arg: nodes.FunctionArgument | null, 144 | func: nodes.Function, 145 | result: CompletionList, 146 | ): CompletionList 147 | getCompletionsForFunctionDeclaration(decl: nodes.FunctionDeclaration, result: CompletionList): CompletionList 148 | getCompletionsForMixinReference(ref: nodes.MixinReference, result: CompletionList): CompletionList 149 | getTermProposals( 150 | entry: IPropertyData | undefined, 151 | existingNode: nodes.Node | null, 152 | result: CompletionList, 153 | ): CompletionList 154 | private makeTermProposal 155 | getCompletionsForSupportsCondition( 156 | supportsCondition: nodes.SupportsCondition, 157 | result: CompletionList, 158 | ): CompletionList 159 | getCompletionsForSupports(supports: nodes.Supports, result: CompletionList): CompletionList 160 | getCompletionsForExtendsReference( 161 | extendsRef: nodes.ExtendsReference, 162 | existingNode: nodes.Node | null, 163 | result: CompletionList, 164 | ): CompletionList 165 | getCompletionForUriLiteralValue(uriLiteralNode: nodes.Node, result: CompletionList): CompletionList 166 | getCompletionForImportPath(importPathNode: nodes.Node, result: CompletionList): CompletionList 167 | private hasCharacterAtPosition 168 | private doesSupportMarkdown 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssFolding.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssFolding" { 2 | import { FoldingRange, TextDocument } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | export function getFoldingRanges( 4 | document: TextDocument, 5 | context: { 6 | rangeLimit?: number 7 | }, 8 | ): FoldingRange[] 9 | } 10 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssHover.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssHover" { 2 | import { 3 | ClientCapabilities, 4 | Hover, 5 | HoverSettings, 6 | Position, 7 | TextDocument, 8 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 9 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 10 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 11 | export class CSSHover { 12 | private readonly clientCapabilities 13 | private readonly cssDataManager 14 | private supportsMarkdown 15 | private readonly selectorPrinting 16 | private defaultSettings? 17 | constructor(clientCapabilities: ClientCapabilities | undefined, cssDataManager: CSSDataManager) 18 | configure(settings: HoverSettings | undefined): void 19 | doHover( 20 | document: TextDocument, 21 | position: Position, 22 | stylesheet: nodes.Stylesheet, 23 | settings?: HoverSettings | undefined, 24 | ): Hover | null 25 | private convertContents 26 | private doesSupportMarkdown 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssNavigation.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssNavigation" { 2 | import { 3 | Color, 4 | ColorInformation, 5 | ColorPresentation, 6 | DocumentContext, 7 | DocumentHighlight, 8 | DocumentLink, 9 | FileSystemProvider, 10 | Location, 11 | Position, 12 | Range, 13 | SymbolInformation, 14 | TextDocument, 15 | WorkspaceEdit, 16 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 17 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 18 | export class CSSNavigation { 19 | protected fileSystemProvider: FileSystemProvider | undefined 20 | constructor(fileSystemProvider: FileSystemProvider | undefined) 21 | findDefinition(document: TextDocument, position: Position, stylesheet: nodes.Node): Location | null 22 | findReferences(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Location[] 23 | findDocumentHighlights( 24 | document: TextDocument, 25 | position: Position, 26 | stylesheet: nodes.Stylesheet, 27 | ): DocumentHighlight[] 28 | protected isRawStringDocumentLinkNode(node: nodes.Node): boolean 29 | findDocumentLinks( 30 | document: TextDocument, 31 | stylesheet: nodes.Stylesheet, 32 | documentContext: DocumentContext, 33 | ): DocumentLink[] 34 | findDocumentLinks2( 35 | document: TextDocument, 36 | stylesheet: nodes.Stylesheet, 37 | documentContext: DocumentContext, 38 | ): Promise 39 | private findUnresolvedLinks 40 | findDocumentSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): SymbolInformation[] 41 | findDocumentColors(document: TextDocument, stylesheet: nodes.Stylesheet): ColorInformation[] 42 | getColorPresentations( 43 | document: TextDocument, 44 | stylesheet: nodes.Stylesheet, 45 | color: Color, 46 | range: Range, 47 | ): ColorPresentation[] 48 | doRename( 49 | document: TextDocument, 50 | position: Position, 51 | newName: string, 52 | stylesheet: nodes.Stylesheet, 53 | ): WorkspaceEdit 54 | protected resolveRelativeReference( 55 | ref: string, 56 | documentUri: string, 57 | documentContext: DocumentContext, 58 | isRawLink?: boolean, 59 | ): Promise 60 | private resolvePathToModule 61 | protected fileExists(uri: string): Promise 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssSelectionRange.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssSelectionRange" { 2 | import { Position, SelectionRange, TextDocument } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import { Stylesheet } from "vscode-css-languageservice/lib/esm/parser/cssNodes" 4 | export function getSelectionRanges( 5 | document: TextDocument, 6 | positions: Position[], 7 | stylesheet: Stylesheet, 8 | ): SelectionRange[] 9 | } 10 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/cssValidation.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/cssValidation" { 2 | import { Diagnostic, LanguageSettings, TextDocument } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 4 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 5 | export class CSSValidation { 6 | private cssDataManager 7 | private settings? 8 | constructor(cssDataManager: CSSDataManager) 9 | configure(settings?: LanguageSettings): void 10 | doValidation( 11 | document: TextDocument, 12 | stylesheet: nodes.Stylesheet, 13 | settings?: LanguageSettings | undefined, 14 | ): Diagnostic[] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/lessCompletion.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/lessCompletion" { 2 | import { 3 | CompletionList, 4 | IPropertyData, 5 | LanguageServiceOptions, 6 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 7 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 8 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 9 | import { CSSCompletion } from "vscode-css-languageservice/lib/esm/services/cssCompletion" 10 | export class LESSCompletion extends CSSCompletion { 11 | private static builtInProposals 12 | private static colorProposals 13 | constructor(lsOptions: LanguageServiceOptions, cssDataManager: CSSDataManager) 14 | private createFunctionProposals 15 | getTermProposals( 16 | entry: IPropertyData | undefined, 17 | existingNode: nodes.Node, 18 | result: CompletionList, 19 | ): CompletionList 20 | protected getColorProposals( 21 | entry: IPropertyData, 22 | existingNode: nodes.Node, 23 | result: CompletionList, 24 | ): CompletionList 25 | getCompletionsForDeclarationProperty(declaration: nodes.Declaration, result: CompletionList): CompletionList 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/lint.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/lessCompletion" { 2 | import { TextDocument } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 4 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 5 | import { LintConfigurationSettings } from "vscode-css-languageservice/lib/esm/services/lintRules" 6 | export class LintVisitor implements nodes.IVisitor { 7 | private cssDataManager 8 | static entries( 9 | node: nodes.Node, 10 | document: TextDocument, 11 | settings: LintConfigurationSettings, 12 | cssDataManager: CSSDataManager, 13 | entryFilter?: number, 14 | ): nodes.IMarker[] 15 | static prefixes: string[] 16 | private warnings 17 | private settings 18 | private keyframes 19 | private documentText 20 | private validProperties 21 | private constructor() 22 | private isValidPropertyDeclaration 23 | private fetch 24 | private fetchWithValue 25 | private findValueInExpression 26 | getEntries(filter?: number): nodes.IMarker[] 27 | private addEntry 28 | private getMissingNames 29 | visitNode(node: nodes.Node): boolean 30 | private completeValidations 31 | private visitUnknownAtRule 32 | private visitKeyframe 33 | private validateKeyframes 34 | private visitSimpleSelector 35 | private visitIdentifierSelector 36 | private visitImport 37 | private visitRuleSet 38 | private visitPrio 39 | private visitNumericValue 40 | private visitFontFace 41 | private isCSSDeclaration 42 | private visitHexColorValue 43 | private visitFunction 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/lintRules.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/lintRules" { 2 | import { LintSettings } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 4 | export class Rule implements nodes.IRule { 5 | id: string 6 | message: string 7 | defaultValue: nodes.Level 8 | constructor(id: string, message: string, defaultValue: nodes.Level) 9 | } 10 | export class Setting { 11 | id: string 12 | message: string 13 | defaultValue: any 14 | constructor(id: string, message: string, defaultValue: any) 15 | } 16 | export const Rules: { 17 | AllVendorPrefixes: Rule 18 | IncludeStandardPropertyWhenUsingVendorPrefix: Rule 19 | DuplicateDeclarations: Rule 20 | EmptyRuleSet: Rule 21 | ImportStatemement: Rule 22 | BewareOfBoxModelSize: Rule 23 | UniversalSelector: Rule 24 | ZeroWithUnit: Rule 25 | RequiredPropertiesForFontFace: Rule 26 | HexColorLength: Rule 27 | ArgsInColorFunction: Rule 28 | UnknownProperty: Rule 29 | UnknownAtRules: Rule 30 | IEStarHack: Rule 31 | UnknownVendorSpecificProperty: Rule 32 | PropertyIgnoredDueToDisplay: Rule 33 | AvoidImportant: Rule 34 | AvoidFloat: Rule 35 | AvoidIdSelector: Rule 36 | } 37 | export const Settings: { 38 | ValidProperties: Setting 39 | } 40 | export class LintConfigurationSettings { 41 | private conf 42 | constructor(conf?: LintSettings) 43 | getRule(rule: Rule): nodes.Level 44 | getSetting(setting: Setting): any 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/lintUtil.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/lintUtil" { 2 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 3 | export class Element { 4 | readonly fullPropertyName: string 5 | readonly node: nodes.Declaration 6 | constructor(decl: nodes.Declaration) 7 | } 8 | interface SideState { 9 | value: boolean 10 | properties: Element[] 11 | } 12 | interface BoxModel { 13 | width?: Element 14 | height?: Element 15 | top: SideState 16 | right: SideState 17 | bottom: SideState 18 | left: SideState 19 | } 20 | export default function calculateBoxModel(propertyTable: Element[]): BoxModel 21 | } 22 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/pathCompletion.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/pathCompletion" { 2 | import { 3 | CompletionList, 4 | DocumentContext, 5 | FileType, 6 | ICompletionParticipant, 7 | ImportPathCompletionContext, 8 | TextDocument, 9 | URILiteralCompletionContext, 10 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 11 | import { DocumentUri } from "vscode-languageserver-types" 12 | export class PathCompletionParticipant implements ICompletionParticipant { 13 | private readonly readDirectory 14 | private literalCompletions 15 | private importCompletions 16 | constructor(readDirectory: (uri: DocumentUri) => Promise<[string, FileType][]>) 17 | onCssURILiteralValue(context: URILiteralCompletionContext): void 18 | onCssImportPath(context: ImportPathCompletionContext): void 19 | computeCompletions(document: TextDocument, documentContext: DocumentContext): Promise 20 | private providePathSuggestions 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/scssCompletion.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/scssCompletion" { 2 | import { 3 | CompletionList, 4 | IPropertyData, 5 | LanguageServiceOptions, 6 | } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 7 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 8 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 9 | import { CSSCompletion } from "vscode-css-languageservice/lib/esm/services/cssCompletion" 10 | export class SCSSCompletion extends CSSCompletion { 11 | private static variableDefaults 12 | private static colorProposals 13 | private static selectorFuncs 14 | private static builtInFuncs 15 | private static scssAtDirectives 16 | private static scssModuleLoaders 17 | private static scssModuleBuiltIns 18 | constructor(lsServiceOptions: LanguageServiceOptions, cssDataManager: CSSDataManager) 19 | protected isImportPathParent(type: nodes.NodeType): boolean 20 | getCompletionForImportPath(importPathNode: nodes.Node, result: CompletionList): CompletionList 21 | private createReplaceFunction 22 | private createFunctionProposals 23 | getCompletionsForSelector( 24 | ruleSet: nodes.RuleSet | null, 25 | isNested: boolean, 26 | result: CompletionList, 27 | ): CompletionList 28 | getTermProposals( 29 | entry: IPropertyData | undefined, 30 | existingNode: nodes.Node, 31 | result: CompletionList, 32 | ): CompletionList 33 | protected getColorProposals( 34 | entry: IPropertyData, 35 | existingNode: nodes.Node, 36 | result: CompletionList, 37 | ): CompletionList 38 | getCompletionsForDeclarationProperty(declaration: nodes.Declaration, result: CompletionList): CompletionList 39 | getCompletionsForExtendsReference( 40 | _extendsRef: nodes.ExtendsReference, 41 | existingNode: nodes.Node, 42 | result: CompletionList, 43 | ): CompletionList 44 | getCompletionForAtDirectives(result: CompletionList): CompletionList 45 | getCompletionForTopLevel(result: CompletionList): CompletionList 46 | getCompletionForModuleLoaders(result: CompletionList): CompletionList 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/scssNavigation.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/scssNavigation" { 2 | import { DocumentContext, FileSystemProvider } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 4 | import { CSSNavigation } from "vscode-css-languageservice/lib/esm/services/cssNavigation" 5 | export class SCSSNavigation extends CSSNavigation { 6 | constructor(fileSystemProvider: FileSystemProvider | undefined) 7 | protected isRawStringDocumentLinkNode(node: nodes.Node): boolean 8 | protected resolveRelativeReference( 9 | ref: string, 10 | documentUri: string, 11 | documentContext: DocumentContext, 12 | isRawLink?: boolean, 13 | ): Promise 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/typings/vscode-css-languageservice/lib/esm/services/selectorPrinting.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vscode-css-languageservice/lib/esm/services/selectorPrinting" { 2 | import { MarkedString } from "vscode-css-languageservice/lib/esm/cssLanguageTypes" 3 | import { CSSDataManager } from "vscode-css-languageservice/lib/esm/languageFacts/dataManager" 4 | import * as nodes from "vscode-css-languageservice/lib/esm/parser/cssNodes" 5 | export class Element { 6 | parent: Element | null 7 | children: Element[] | null 8 | attributes: 9 | | { 10 | name: string 11 | value: string 12 | }[] 13 | | null 14 | findAttribute(name: string): string | null 15 | addChild(child: Element): void 16 | append(text: string): void 17 | prepend(text: string): void 18 | findRoot(): Element 19 | removeChild(child: Element): boolean 20 | addAttr(name: string, value: string): void 21 | clone(cloneChildren?: boolean): Element 22 | cloneWithParent(): Element 23 | } 24 | export class RootElement extends Element {} 25 | export class LabelElement extends Element { 26 | constructor(label: string) 27 | } 28 | export function toElement(node: nodes.SimpleSelector, parentElement?: Element | null): Element 29 | export class SelectorPrinting { 30 | private cssDataManager 31 | constructor(cssDataManager: CSSDataManager) 32 | selectorToMarkedString(node: nodes.Selector): MarkedString[] 33 | simpleSelectorToMarkedString(node: nodes.SimpleSelector): MarkedString[] 34 | private isPseudoElementIdentifier 35 | private selectorToSpecificityMarkedString 36 | } 37 | export function selectorToElement(node: nodes.Selector): Element | null 38 | } 39 | -------------------------------------------------------------------------------- /syntaxes/injection.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "source.template.twin", 3 | "scopeName": "source.template.twin", 4 | "injectionSelector": "L:source -comment", 5 | "patterns": [ 6 | { 7 | "begin": "\\s*(tw)(?:(?:(?:\\.[\\w$]+)*|(\\([^\\)]*\\))))*(`)", 8 | "end": "`", 9 | "contentName": "meta.embedded.twin", 10 | "beginCaptures": { 11 | "1": { "name": "entity.name.function.tagged-template" }, 12 | "2": { 13 | "patterns": [ 14 | { "include": "source.ts#comment" }, 15 | { "include": "source.ts#string" }, 16 | { "include": "source.ts#expression" } 17 | ] 18 | } 19 | }, 20 | "patterns": [ 21 | { 22 | "end": "`", 23 | "patterns": [ 24 | { "include": "#line-comment-end-backtick" }, 25 | { "include": "#block-comment-end-backtick" }, 26 | { "include": "source.twin" } 27 | ] 28 | } 29 | ] 30 | }, 31 | { 32 | "begin": "\\s*(theme)(`)", 33 | "end": "`", 34 | "contentName": "meta.embedded.twin", 35 | "beginCaptures": { 36 | "1": { "name": "entity.name.function.tagged-template" } 37 | } 38 | } 39 | ], 40 | "repository": { 41 | "line-comment-end-backtick": { 42 | "match": "(//)[^`]*", 43 | "captures": { 44 | "0": { "name": "comment.line.double-slash.twin" }, 45 | "1": { "name": "punctuation.definition.comment.twin" } 46 | } 47 | }, 48 | "block-comment-end-backtick": { 49 | "begin": "(/\\*)", 50 | "beginCaptures": { 51 | "1": { "name": "punctuation.definition.comment.begin.twin" } 52 | }, 53 | "end": "(\\*/)|`", 54 | "endCaptures": { 55 | "1": { "name": "punctuation.definition.comment.end.twin" } 56 | }, 57 | "contentName": "comment.block.twin" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /syntaxes/injectionCs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twin", 3 | "scopeName": "source.tag.cs.twin", 4 | "injectionSelector": "L:meta.tag.attributes -comment", 5 | "patterns": [ 6 | { 7 | "name": "meta.tag.cs.twin", 8 | "begin": "\\s*?(cs)(=)(\")", 9 | "beginCaptures": { 10 | "1": { "name": "entity.other.attribute-name" }, 11 | "2": { "name": "keyword.operator.assignment" }, 12 | "3": { "name": "string.quoted.double" } 13 | }, 14 | "end": "(\")", 15 | "endCaptures": { 16 | "1": { "name": "string.quoted.double" } 17 | }, 18 | 19 | "patterns": [ 20 | { 21 | "end": "\"", 22 | "patterns": [ 23 | { "include": "#line-comment-end-double-quote" }, 24 | { "include": "#block-comment-end-double-quote" }, 25 | { "include": "source.twin" } 26 | ] 27 | } 28 | ] 29 | }, 30 | { 31 | "name": "meta.tag.cs.twin", 32 | "begin": "\\s*(cs)(=)(')", 33 | "beginCaptures": { 34 | "1": { "name": "entity.other.attribute-name" }, 35 | "2": { "name": "keyword.operator.assignment" }, 36 | "3": { "name": "string.quoted.single" } 37 | }, 38 | "end": "(')", 39 | "endCaptures": { 40 | "1": { 41 | "name": "string.quoted.single" 42 | } 43 | }, 44 | "patterns": [ 45 | { 46 | "end": "'", 47 | "patterns": [ 48 | { "include": "#line-comment-end-single-quote" }, 49 | { "include": "#block-comment-end-single-quote" }, 50 | { "include": "source.twin" } 51 | ] 52 | } 53 | ] 54 | }, 55 | { 56 | "name": "meta.tag.cs.twin", 57 | "begin": "\\s*(cs)(=){(\")", 58 | "beginCaptures": { 59 | "1": { "name": "entity.other.attribute-name" }, 60 | "2": { "name": "keyword.operator.assignment" }, 61 | "3": { "name": "string.quote.double" } 62 | }, 63 | "end": "(\")}", 64 | "endCaptures": { 65 | "1": { "name": "string.quote.double" } 66 | }, 67 | "patterns": [ 68 | { 69 | "end": "\"}", 70 | "patterns": [ 71 | { "include": "#line-comment-end-double-quote" }, 72 | { "include": "#block-comment-end-double-quote" }, 73 | { "include": "source.twin" } 74 | ] 75 | } 76 | ] 77 | }, 78 | { 79 | "name": "meta.tag.cs.twin", 80 | "begin": "\\s*(cs)(=){(')", 81 | "beginCaptures": { 82 | "1": { "name": "entity.other.attribute-name" }, 83 | "2": { "name": "keyword.operator.assignment" }, 84 | "3": { "name": "string.quote.single" } 85 | }, 86 | "end": "(')}", 87 | "endCaptures": { 88 | "1": { "name": "string.quote.single" } 89 | }, 90 | "patterns": [ 91 | { 92 | "end": "\"}", 93 | "patterns": [ 94 | { "include": "#line-comment-end-single-quote" }, 95 | { "include": "#block-comment-end-single-quote" }, 96 | { "include": "source.twin" } 97 | ] 98 | } 99 | ] 100 | }, 101 | { 102 | "name": "meta.tag.tw.twin", 103 | "begin": "\\s*(cs)(=){`", 104 | "beginCaptures": { 105 | "1": { "name": "entity.other.attribute-name" }, 106 | "2": { "name": "keyword.operator.assignment" } 107 | }, 108 | "end": "`}", 109 | "patterns": [ 110 | { 111 | "end": "\"}", 112 | "patterns": [ 113 | { "include": "#line-comment-end-backtick" }, 114 | { "include": "#block-comment-end-backtick" }, 115 | { "include": "source.twin" } 116 | ] 117 | } 118 | ] 119 | } 120 | ], 121 | "repository": { 122 | "line-comment-end-double-quote": { 123 | "match": "(//)[^\"]*", 124 | "captures": { 125 | "0": { "name": "comment.line.double-slash.twin" }, 126 | "1": { "name": "punctuation.definition.comment.twin" } 127 | } 128 | }, 129 | "block-comment-end-double-quote": { 130 | "begin": "(/\\*)", 131 | "beginCaptures": { 132 | "1": { "name": "punctuation.definition.comment.begin.twin" } 133 | }, 134 | "end": "(\\*/)|\"", 135 | "endCaptures": { 136 | "1": { "name": "punctuation.definition.comment.end.twin" } 137 | }, 138 | "contentName": "comment.block.twin" 139 | }, 140 | "line-comment-end-single-quote": { 141 | "match": "(//)[^']*", 142 | "captures": { 143 | "0": { "name": "comment.line.double-slash.twin" }, 144 | "1": { "name": "punctuation.definition.comment.twin" } 145 | } 146 | }, 147 | "block-comment-end-single-quote": { 148 | "begin": "(/\\*)", 149 | "beginCaptures": { 150 | "1": { "name": "punctuation.definition.comment.begin.twin" } 151 | }, 152 | "end": "(\\*/)|'", 153 | "endCaptures": { 154 | "1": { "name": "punctuation.definition.comment.end.twin" } 155 | }, 156 | "contentName": "comment.block.twin" 157 | }, 158 | "line-comment-end-backtick": { 159 | "match": "(//)[^`]*", 160 | "captures": { 161 | "0": { "name": "comment.line.double-slash.twin" }, 162 | "1": { "name": "punctuation.definition.comment.twin" } 163 | } 164 | }, 165 | "block-comment-end-backtick": { 166 | "begin": "(/\\*)", 167 | "beginCaptures": { 168 | "1": { "name": "punctuation.definition.comment.begin.twin" } 169 | }, 170 | "end": "(\\*/)|`", 171 | "endCaptures": { 172 | "1": { "name": "punctuation.definition.comment.end.twin" } 173 | }, 174 | "contentName": "comment.block.twin" 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /syntaxes/injectionTag.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twin", 3 | "scopeName": "source.tag.tw.twin", 4 | "injectionSelector": "L:meta.tag.attributes -comment", 5 | "patterns": [ 6 | { 7 | "name": "meta.tag.tw.twin", 8 | "begin": "\\s*(tw)(=)(\")", 9 | "beginCaptures": { 10 | "1": { "name": "entity.other.attribute-name" }, 11 | "2": { "name": "keyword.operator.assignment" }, 12 | "3": { "name": "string.quoted.double" } 13 | }, 14 | "end": "(\")", 15 | "endCaptures": { 16 | "1": { "name": "string.quoted.double" } 17 | }, 18 | 19 | "patterns": [ 20 | { 21 | "end": "\"", 22 | "patterns": [ 23 | { "include": "#line-comment-end-double-quote" }, 24 | { "include": "#block-comment-end-double-quote" }, 25 | { "include": "source.twin" } 26 | ] 27 | } 28 | ] 29 | }, 30 | { 31 | "name": "meta.tag.tw.twin", 32 | "begin": "\\s*(tw)(=)(')", 33 | "beginCaptures": { 34 | "1": { "name": "entity.other.attribute-name" }, 35 | "2": { "name": "keyword.operator.assignment" }, 36 | "3": { "name": "string.quoted.single" } 37 | }, 38 | "end": "(')", 39 | "endCaptures": { 40 | "1": { 41 | "name": "string.quoted.single" 42 | } 43 | }, 44 | "patterns": [ 45 | { 46 | "end": "'", 47 | "patterns": [ 48 | { "include": "#line-comment-end-single-quote" }, 49 | { "include": "#block-comment-end-single-quote" }, 50 | { "include": "source.twin" } 51 | ] 52 | } 53 | ] 54 | }, 55 | { 56 | "name": "meta.tag.tw.twin", 57 | "begin": "\\s*(tw)(=){(\")", 58 | "beginCaptures": { 59 | "1": { "name": "entity.other.attribute-name" }, 60 | "2": { "name": "keyword.operator.assignment" }, 61 | "3": { "name": "string.quote.double" } 62 | }, 63 | "end": "(\")}", 64 | "endCaptures": { 65 | "1": { "name": "string.quote.double" } 66 | }, 67 | "patterns": [ 68 | { 69 | "end": "\"}", 70 | "patterns": [ 71 | { "include": "#line-comment-end-double-quote" }, 72 | { "include": "#block-comment-end-double-quote" }, 73 | { "include": "source.twin" } 74 | ] 75 | } 76 | ] 77 | }, 78 | { 79 | "name": "meta.tag.tw.twin", 80 | "begin": "\\s*(tw)(=){(')", 81 | "beginCaptures": { 82 | "1": { "name": "entity.other.attribute-name" }, 83 | "2": { "name": "keyword.operator.assignment" }, 84 | "3": { "name": "string.quote.single" } 85 | }, 86 | "end": "(')}", 87 | "endCaptures": { 88 | "1": { "name": "string.quote.single" } 89 | }, 90 | "patterns": [ 91 | { 92 | "end": "\"}", 93 | "patterns": [ 94 | { "include": "#line-comment-end-single-quote" }, 95 | { "include": "#block-comment-end-single-quote" }, 96 | { "include": "source.twin" } 97 | ] 98 | } 99 | ] 100 | }, 101 | { 102 | "name": "meta.tag.tw.twin", 103 | "begin": "\\s*(tw)(=){`", 104 | "beginCaptures": { 105 | "1": { "name": "entity.other.attribute-name" }, 106 | "2": { "name": "keyword.operator.assignment" } 107 | }, 108 | "end": "`}", 109 | "patterns": [ 110 | { 111 | "end": "\"}", 112 | "patterns": [ 113 | { "include": "#line-comment-end-backtick" }, 114 | { "include": "#block-comment-end-backtick" }, 115 | { "include": "source.twin" } 116 | ] 117 | } 118 | ] 119 | } 120 | ], 121 | "repository": { 122 | "line-comment-end-double-quote": { 123 | "match": "(//)[^\"]*", 124 | "captures": { 125 | "0": { "name": "comment.line.double-slash.twin" }, 126 | "1": { "name": "punctuation.definition.comment.twin" } 127 | } 128 | }, 129 | "block-comment-end-double-quote": { 130 | "begin": "(/\\*)", 131 | "beginCaptures": { 132 | "1": { "name": "punctuation.definition.comment.begin.twin" } 133 | }, 134 | "end": "(\\*/)|\"", 135 | "endCaptures": { 136 | "1": { "name": "punctuation.definition.comment.end.twin" } 137 | }, 138 | "contentName": "comment.block.twin" 139 | }, 140 | "line-comment-end-single-quote": { 141 | "match": "(//)[^']*", 142 | "captures": { 143 | "0": { "name": "comment.line.double-slash.twin" }, 144 | "1": { "name": "punctuation.definition.comment.twin" } 145 | } 146 | }, 147 | "block-comment-end-single-quote": { 148 | "begin": "(/\\*)", 149 | "beginCaptures": { 150 | "1": { "name": "punctuation.definition.comment.begin.twin" } 151 | }, 152 | "end": "(\\*/)|'", 153 | "endCaptures": { 154 | "1": { "name": "punctuation.definition.comment.end.twin" } 155 | }, 156 | "contentName": "comment.block.twin" 157 | }, 158 | "line-comment-end-backtick": { 159 | "match": "(//)[^`]*", 160 | "captures": { 161 | "0": { "name": "comment.line.double-slash.twin" }, 162 | "1": { "name": "punctuation.definition.comment.twin" } 163 | } 164 | }, 165 | "block-comment-end-backtick": { 166 | "begin": "(/\\*)", 167 | "beginCaptures": { 168 | "1": { "name": "punctuation.definition.comment.begin.twin" } 169 | }, 170 | "end": "(\\*/)|`", 171 | "endCaptures": { 172 | "1": { "name": "punctuation.definition.comment.end.twin" } 173 | }, 174 | "contentName": "comment.block.twin" 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /syntaxes/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [ 7 | ["[", "]"], 8 | ["(", ")"] 9 | ], 10 | "autoClosingPairs": [ 11 | { "open": "[", "close": "]" }, 12 | { "open": "(", "close": ")" }, 13 | { "open": "'", "close": "'", "notIn": ["string", "comment"] }, 14 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 15 | { "open": "`", "close": "`", "notIn": ["string", "comment"] }, 16 | { "open": "/**", "close": " */", "notIn": ["string"] } 17 | ], 18 | "surroundingPairs": [ 19 | ["[", "]"], 20 | ["(", ")"], 21 | ["'", "'"], 22 | ["\"", "\""], 23 | ["`", "`"] 24 | ], 25 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", 26 | "indentationRules": { 27 | "increaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$", 28 | "decreaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /syntaxes/twin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twin", 3 | "scopeName": "source.twin", 4 | "patterns": [{ "include": "#expression" }], 5 | "repository": { 6 | "expression": { 7 | "patterns": [ 8 | { "include": "#line-comment" }, 9 | { "include": "#block-comment" }, 10 | { "include": "#arbitrary-variant-or-property" }, 11 | { "include": "#variant" }, 12 | { "include": "#short-css" }, 13 | { "include": "#arbitrary-classname" }, 14 | { "include": "#arbitrary-opacity" }, 15 | { "include": "#group" }, 16 | { "include": "#classname" } 17 | ] 18 | }, 19 | "line-comment": { 20 | "match": "(//).*", 21 | "captures": { 22 | "0": { "name": "comment.line.double-slash.twin" }, 23 | "1": { "name": "punctuation.definition.comment.twin" } 24 | } 25 | }, 26 | "block-comment": { 27 | "begin": "(/\\*)", 28 | "beginCaptures": { 29 | "1": { "name": "punctuation.definition.comment.begin.twin" } 30 | }, 31 | "end": "(\\*/)", 32 | "endCaptures": { 33 | "1": { "name": "punctuation.definition.comment.end.twin" } 34 | }, 35 | "contentName": "comment.block.twin" 36 | }, 37 | "variant": { 38 | "match": "[\\w-]+:", 39 | "captures": { 40 | "0": { "name": "entity.other.inherited-class.variant.twin" } 41 | } 42 | }, 43 | "arbitrary-variant-or-property": { 44 | "begin": "(?!?+*=/])*[\\w\\-\\./]+(!?)(?!?+*=/-])+-)+)\\[", 87 | "end": "\\](?:(\\/\\d+)|\\/\\[(\\s*\\d*\\.?\\d*\\s*)\\])?(!?)", 88 | "beginCaptures": { 89 | "1": { "name": "support.function.important-bang.before.twin" }, 90 | "2": { "name": "support.type.arbitrary-style.prop.twin" } 91 | }, 92 | "endCaptures": { 93 | "1": { "name": "support.constant.classname.twin" }, 94 | "2": { "patterns": [{ "include": "source.css.scss.tw#property_values" }] }, 95 | "3": { "name": "support.function.important-bang.after.twin" } 96 | }, 97 | "patterns": [ 98 | { 99 | "end": "\\]!?", 100 | "patterns": [{ "include": "#track-list" }, { "include": "source.css.scss.tw#property_values" }] 101 | } 102 | ] 103 | }, 104 | "arbitrary-opacity": { 105 | "begin": "(!?)((?:(?:(?!\\/\\/)-?[^\\s:\\(\\)\\[\\]\\{\\}<>!?+*=/-])+-)*(?:(?!\\/\\/)[\\w\\/])+)(/\\[)", 106 | "end": "(\\])(!?)", 107 | "beginCaptures": { 108 | "1": { "name": "support.function.important-bang.before.twin" }, 109 | "2": { "name": "support.type.arbitrary-style.prop.twin" } 110 | }, 111 | "endCaptures": { 112 | "2": { "name": "support.function.important-bang.after.twin" } 113 | }, 114 | "patterns": [{ "include": "source.css.scss.tw#property_values" }] 115 | }, 116 | "short-css": { 117 | "contentName": "entity.name.variable.css-value.twin", 118 | "begin": "(!?)((?:-{1,2})?(?:\\w+-)*\\w+)(\\[)", 119 | "end": "(\\])(!?)", 120 | "beginCaptures": { 121 | "1": { "name": "support.function.important-bang.short-css.twin" }, 122 | "2": { "name": "support.type.short-css.prop.twin" } 123 | }, 124 | "endCaptures": { 125 | "2": { "name": "support.function.important-bang.short-css.twin" } 126 | }, 127 | "patterns": [{ "include": "#track-list" }, { "include": "source.css.scss.tw#property_values" }] 128 | }, 129 | "group": { 130 | "begin": "(!?)(\\()", 131 | "end": "(\\))(!?)", 132 | "beginCaptures": { 133 | "1": { "name": "support.function.important-bang.before.twin" }, 134 | "2": { "name": "punctuation.paren.open" } 135 | }, 136 | "endCaptures": { 137 | "1": { "name": "punctuation.paren.close" }, 138 | "2": { "name": "support.function.important-bang.after.twin" } 139 | }, 140 | "patterns": [{ "include": "#expression" }] 141 | }, 142 | "track-list": { 143 | "contentName": "string.property-value.css", 144 | "begin": "\\[", 145 | "end": "\\]", 146 | "patterns": [{ "include": "#track-list" }] 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": ["ESNext"], 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "isolatedModules": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "exactOptionalPropertyTypes": false, 13 | "resolveJsonModule": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webpack.analyzer.js: -------------------------------------------------------------------------------- 1 | const { register } = require("@swc-node/register/register") 2 | const { readDefaultTsConfig } = require("@swc-node/register/read-default-tsconfig") 3 | const path = require("path") 4 | register(readDefaultTsConfig(path.resolve("tsconfig.json"))) 5 | module.exports = require("./webpack.analyzer.ts").default 6 | -------------------------------------------------------------------------------- /webpack.analyzer.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "webpack-merge" 2 | // @ts-ignore TS/7016 3 | import Visualizer from "webpack-visualizer-plugin2" 4 | import config from "./webpack.config" 5 | 6 | export default merge(config, { 7 | plugins: [ 8 | new Visualizer({ 9 | filename: "../statistics.html", 10 | }), 11 | ], 12 | }) 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { register } = require("@swc-node/register/register") 2 | const { readDefaultTsConfig } = require("@swc-node/register/read-default-tsconfig") 3 | const path = require("path") 4 | register(readDefaultTsConfig(path.resolve("tsconfig.json"))) 5 | module.exports = require("./webpack.config.ts").default 6 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process" 2 | import { CleanWebpackPlugin } from "clean-webpack-plugin" 3 | import CopyPlugin from "copy-webpack-plugin" 4 | import ESLintPlugin from "eslint-webpack-plugin" 5 | import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin" 6 | import path from "path" 7 | import TerserPlugin from "terser-webpack-plugin" 8 | import TsPathsResolvePlugin from "ts-paths-resolve-plugin" 9 | import type { Compiler, Configuration } from "webpack" 10 | import { DefinePlugin, ExternalsPlugin } from "webpack" 11 | 12 | class ExternalsVendorPlugin { 13 | externals: Record 14 | constructor(...deps: string[]) { 15 | this.externals = {} 16 | for (const dep of deps) { 17 | this.externals[dep] = dep 18 | } 19 | } 20 | apply(compiler: Compiler) { 21 | new ExternalsPlugin("commonjs", this.externals).apply(compiler) 22 | } 23 | } 24 | 25 | const clientWorkspaceFolder = path.resolve(__dirname, "src") 26 | 27 | const configExtension: Configuration = { 28 | target: "node", 29 | mode: process.env.NODE_ENV === "production" ? "production" : "development", 30 | devtool: "source-map", 31 | entry: path.join(clientWorkspaceFolder, "extension.ts"), 32 | output: { 33 | path: path.resolve(__dirname, "dist"), 34 | filename: "extension.js", 35 | libraryTarget: "commonjs2", 36 | devtoolModuleFilenameTemplate: "[absolute-resource-path]", 37 | }, 38 | optimization: { 39 | minimize: true, 40 | minimizer: [ 41 | new TerserPlugin({ 42 | parallel: true, 43 | minify: TerserPlugin.esbuildMinify, 44 | }), 45 | ], 46 | }, 47 | module: { 48 | rules: [ 49 | { 50 | test: /\.ts$/, 51 | exclude: /node_modules|\.test\.ts$/, 52 | use: { 53 | loader: "swc-loader", 54 | options: { 55 | jsc: { 56 | parser: { 57 | syntax: "typescript", 58 | }, 59 | target: "es2020", 60 | }, 61 | module: { 62 | type: "commonjs", 63 | }, 64 | }, 65 | }, 66 | }, 67 | { 68 | test: /\.ya?ml$/, 69 | use: "js-yaml-loader", 70 | }, 71 | { 72 | test: /.node$/, 73 | loader: "node-loader", 74 | }, 75 | ], 76 | // NOTE: https://github.com/microsoft/TypeScript/issues/39436 77 | noParse: [require.resolve("typescript/lib/typescript.js")], 78 | }, 79 | resolve: { 80 | extensions: [".ts", ".js", ".json"], 81 | }, 82 | plugins: [ 83 | new TsPathsResolvePlugin({ tsConfigPath: path.resolve(clientWorkspaceFolder, "tsconfig.json") }), 84 | new ForkTsCheckerPlugin({ 85 | typescript: { 86 | configFile: path.resolve(clientWorkspaceFolder, "tsconfig.json"), 87 | }, 88 | }), 89 | new ExternalsVendorPlugin("vscode"), 90 | new ESLintPlugin({ extensions: ["ts"] }), 91 | new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ["extension*"] }), 92 | new CopyPlugin({ 93 | patterns: [{ from: "node_modules/tailwindcss/lib/css", to: "css" }], 94 | }), 95 | new DefinePlugin({ 96 | __COMMIT_HASH__: JSON.stringify(execSync("git rev-parse HEAD").toString().trim()), 97 | }), 98 | ], 99 | } 100 | 101 | export default configExtension 102 | --------------------------------------------------------------------------------