mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'master' into extension-viewlet
This commit is contained in:
commit
a71d6f5ce5
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,8 +7,7 @@ out/
|
|||
out-build/
|
||||
out-editor/
|
||||
out-editor-min/
|
||||
out-editor-ossfree/
|
||||
out-editor-ossfree-min/
|
||||
out-monaco-editor-core/
|
||||
out-vscode/
|
||||
out-vscode-min/
|
||||
build/node_modules
|
4
.mention-bot
Normal file
4
.mention-bot
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"maxReviewers": 2,
|
||||
"userBlacklistForPR": ["alexandrudima", "aeschli", "weinand", "bpasero", "isidorn", "joaomoreno", "jrieken", "dbaeumer", "egamma"]
|
||||
}
|
|
@ -21,9 +21,8 @@ before_install:
|
|||
- git submodule update --init --recursive
|
||||
- git clone https://github.com/creationix/nvm.git ./.nvm
|
||||
- source ./.nvm/nvm.sh
|
||||
- nvm install node
|
||||
- nvm use node
|
||||
- npm install -g npm@2
|
||||
- nvm install 5.10
|
||||
- nvm use 5.10
|
||||
- npm config set python `which python`
|
||||
- npm install -g gulp
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then
|
||||
|
|
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
|
@ -7,10 +7,28 @@
|
|||
"request": "launch",
|
||||
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
"--timeout",
|
||||
"999999"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeArgs": [],
|
||||
"env": {},
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/out"
|
||||
},
|
||||
{
|
||||
"name": "Stacks Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
"--timeout",
|
||||
"999999",
|
||||
"--colors"
|
||||
"--colors",
|
||||
"-g",
|
||||
"Stacks"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeArgs": [],
|
||||
|
@ -74,6 +92,21 @@
|
|||
"request": "launch",
|
||||
"program": "${workspaceRoot}/src/vs/languages/css/common/buildscripts/generate_browserjs.js",
|
||||
"stopOnEntry": false
|
||||
},
|
||||
{
|
||||
"name": "Debug monaco",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/lib/monaco.js",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
],
|
||||
"cwd": "${workspaceRoot}/build/lib"
|
||||
// ,
|
||||
|
||||
// "port": 5870,
|
||||
// "sourceMaps": true,
|
||||
// "outDir": "${workspaceRoot}/out"
|
||||
}
|
||||
]
|
||||
}
|
1
.vscode/tasks.json
vendored
1
.vscode/tasks.json
vendored
|
@ -20,6 +20,7 @@
|
|||
"isWatching": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"absolute"
|
||||
],
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
### Before Submitting an Issue
|
||||
First, please do a search in open issues to see if the issue or feature request has already been filed. If there is an issue add your comments to this issue.
|
||||
|
||||
The Code project is distributed across multiple repositories, try to file the issue against the correct repository [Related Projects](https://github.com/Microsoft/vscode/tree/master/wiki/project-management/related-projects.md).
|
||||
The Code project is distributed across multiple repositories, try to file the issue against the correct repository [Related Projects](https://github.com/Microsoft/vscode/wiki/Related-Projects).
|
||||
|
||||
If your issue is a question then please ask the question on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode) using the tag `vscode`.
|
||||
|
||||
|
@ -13,7 +13,7 @@ File a single issue per problem and feature request, do not file combo issues.
|
|||
|
||||
The more information you can provide, the more likely someone will be successful reproducing the issue and finding a fix. Therefore:
|
||||
|
||||
* Provide reproducable steps, what the result of the steps was, an what you would have expected.
|
||||
* Provide reproducable steps, what the result of the steps was, and what you would have expected.
|
||||
* Description of what you expect to happen
|
||||
* Animated GIFs
|
||||
* Code that demonstrates the issue
|
||||
|
@ -23,4 +23,4 @@ The more information you can provide, the more likely someone will be successful
|
|||
|
||||
## Contributing Fixes
|
||||
If you are interested in fixing issues and contributing directly to the code base,
|
||||
please see the document [How to Contribute](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/how-to-contribute.md).
|
||||
please see the document [How to Contribute](https://github.com/Microsoft/vscode/wiki/How-to-Contribute).
|
||||
|
|
107
OSSREADME.json
107
OSSREADME.json
|
@ -2,7 +2,7 @@
|
|||
|
||||
[{
|
||||
"name": "atom-brightray",
|
||||
"repositoryURL": "https://github.com/atom/brightray",
|
||||
"repositoryURL": "https://github.com/electron/brightray",
|
||||
"license": "MIT",
|
||||
"isProd": true
|
||||
},
|
||||
|
@ -46,7 +46,7 @@
|
|||
"name": "libchromiumcontent",
|
||||
"version": "49.0.2623.75",
|
||||
"license": "MIT",
|
||||
"repositoryURL": "https://github.com/atom/libchromiumcontent",
|
||||
"repositoryURL": "https://github.com/electron/libchromiumcontent",
|
||||
"isProd": true
|
||||
},
|
||||
{
|
||||
|
@ -59,7 +59,7 @@
|
|||
"name": "electron",
|
||||
"version": "0.37.6",
|
||||
"license": "MIT",
|
||||
"repositoryURL": "https://github.com/atom/electron",
|
||||
"repositoryURL": "https://github.com/electron/electron",
|
||||
"isProd": true
|
||||
},
|
||||
{
|
||||
|
@ -67,7 +67,7 @@
|
|||
"version": "5.5.6",
|
||||
"repositoryURL": "https://github.com/jrsoftware/issrc",
|
||||
"isProd": true
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
@ -75,75 +75,18 @@
|
|||
// defining licenses for dependencies where licensing cannot
|
||||
// be inferred by tooling:
|
||||
|
||||
{
|
||||
// Reason: LICENSE file missing in repo
|
||||
// SOURCE: https://github.com/isaacs/core-util-is/blob/master/lib/util.js
|
||||
"name":"core-util-is",
|
||||
"isLicense": true,
|
||||
"licenseDetail": [
|
||||
"Copyright Joyent, Inc. and other Node contributors.",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a",
|
||||
"copy of this software and associated documentation files (the",
|
||||
"\"Software\"), to deal in the Software without restriction, including",
|
||||
"without limitation the rights to use, copy, modify, merge, publish,",
|
||||
"distribute, sublicense, and/or sell copies of the Software, and to permit",
|
||||
"persons to whom the Software is furnished to do so, subject to the",
|
||||
"following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included",
|
||||
"in all copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS",
|
||||
"OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF",
|
||||
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN",
|
||||
"NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,",
|
||||
"DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR",
|
||||
"OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE",
|
||||
"USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
]
|
||||
},
|
||||
{
|
||||
// Reason: windows-mutex is not public yet
|
||||
// TODO: Remove once windows-mutex visibility is made public
|
||||
"name": "windows-mutex",
|
||||
"isLicense": true,
|
||||
"licenseDetail": [
|
||||
"The MIT License (MIT)",
|
||||
"",
|
||||
"Copyright (c) Microsoft Corporation",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy",
|
||||
"of this software and associated documentation files (the \"Software\"), to deal",
|
||||
"in the Software without restriction, including without limitation the rights",
|
||||
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
|
||||
"copies of the Software, and to permit persons to whom the Software is",
|
||||
"furnished to do so, subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in all",
|
||||
"copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
|
||||
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
|
||||
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
|
||||
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
|
||||
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE",
|
||||
"SOFTWARE."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
/*{
|
||||
// Reason: LICENSE file missing in repo
|
||||
"name": "winreg",
|
||||
"isLicense": true,
|
||||
"licenseDetail": [
|
||||
"Copyright (c) Paul Bottin",
|
||||
"This project is released under BSD 2-Clause License.",
|
||||
"",
|
||||
"The BSD License",
|
||||
"Copyright (c) 2016, Paul Bottin All rights reserved.",
|
||||
"",
|
||||
"Redistribution and use in source and binary forms, with or without",
|
||||
"modification, are permitted provided that the following conditions",
|
||||
"are met:",
|
||||
"Redistribution and use in source and binary forms, with or without modification,",
|
||||
"are permitted provided that the following conditions are met:",
|
||||
"",
|
||||
"1. Redistributions of source code must retain the above copyright",
|
||||
" notice, this list of conditions and the following disclaimer.",
|
||||
|
@ -164,31 +107,5 @@
|
|||
"OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN",
|
||||
"IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
]
|
||||
},
|
||||
{
|
||||
// Reason: LICENSE file doesn't have Copyright statement
|
||||
"name": "readdirp",
|
||||
"isLicense": true,
|
||||
"licenseDetail": [
|
||||
"This software is released under the MIT license:",
|
||||
"",
|
||||
"Copyright (c) Thorsten Lorenz",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy of",
|
||||
"this software and associated documentation files (the \"Software\"), to deal in",
|
||||
"the Software without restriction, including without limitation the rights to",
|
||||
"use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of",
|
||||
"the Software, and to permit persons to whom the Software is furnished to do so,",
|
||||
"subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in all",
|
||||
"copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS",
|
||||
"FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR",
|
||||
"COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER",
|
||||
"IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN",
|
||||
"CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
]
|
||||
}]
|
||||
}*/
|
||||
]
|
||||
|
|
12
README.md
12
README.md
|
@ -18,12 +18,12 @@ The [`vscode`](https://github.com/microsoft/vscode) repository is where we do de
|
|||
|
||||
## Contributing
|
||||
If you are interested in fixing issues and contributing directly to the code base,
|
||||
please see the document [How to Contribute](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/how-to-contribute.md), which covers the following:
|
||||
please see the document [How to Contribute](https://github.com/Microsoft/vscode/wiki/How-to-Contribute), which covers the following:
|
||||
|
||||
* [How to build and run from source](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/how-to-contribute.md#build-and-run-from-source)
|
||||
* [The development workflow, including debugging and running tests](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/how-to-contribute.md#development-workflow)
|
||||
* [Coding Guidelines](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/coding-guidelines.md)
|
||||
* [Submitting pull requests](https://github.com/Microsoft/vscode/tree/master/wiki/contributing/how-to-contribute.md#pull-requests)
|
||||
* [How to build and run from source](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#build-and-run-from-source)
|
||||
* [The development workflow, including debugging and running tests](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#development-workflow)
|
||||
* [Coding Guidelines](https://github.com/Microsoft/vscode/wiki/Coding-Guidelines)
|
||||
* [Submitting pull requests](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#pull-requests)
|
||||
|
||||
## Feedback
|
||||
|
||||
|
@ -35,7 +35,7 @@ please see the document [How to Contribute](https://github.com/Microsoft/vscode/
|
|||
## Related Projects
|
||||
Many of the core components and extensions to Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug).
|
||||
|
||||
For a complete list, please see the [Related Projects](https://github.com/Microsoft/vscode/tree/master/wiki/project-management/related-projects.md) page on our wiki.
|
||||
For a complete list, please see the [Related Projects](https://github.com/Microsoft/vscode/wiki/Related-Projects) page on our wiki.
|
||||
|
||||
## License
|
||||
[MIT](LICENSE.txt)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
microsoft-vscode
|
||||
|
||||
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
|
||||
|
@ -6,59 +5,151 @@ Do Not Translate or Localize
|
|||
|
||||
This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise.
|
||||
|
||||
1. Benvie/JavaScriptNext.tmLanguage (https://github.com/Benvie/JavaScriptNext.tmLanguage)
|
||||
2. chjj-marked version 0.3.2 (https://github.com/npmcomponent/chjj-marked)
|
||||
3. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes)
|
||||
4. davidrios/jade-tmbundle (https://github.com/davidrios/jade-tmbundle)
|
||||
5. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||
6. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift)
|
||||
7. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
|
||||
8. Ionic documentation version 1.2.4 (https://github.com/driftyco/ionic-site)
|
||||
9. ionide/ionide-fsharp (https://github.com/ionide/ionide-fsharp)
|
||||
10. js-beautify version 1.6.2 (https://github.com/beautify-web/js-beautify)
|
||||
11. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert)
|
||||
12. language-docker (https://github.com/docker/docker)
|
||||
13. language-go version 0.39.0 (https://github.com/atom/language-go)
|
||||
14. language-php version 0.29.0 (https://github.com/atom/language-php)
|
||||
15. language-rust version 0.4.4 (https://github.com/zargony/atom-language-rust)
|
||||
16. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage)
|
||||
17. mmcgrana/textmate-clojure (https://github.com/mmcgrana/textmate-clojure)
|
||||
18. octicons-code version 3.1.0 (https://octicons.github.com)
|
||||
19. octicons-font version 3.1.0 (https://octicons.github.com)
|
||||
20. string_scorer version 0.1.20 (https://github.com/joshaven/string_score)
|
||||
21. sublimehq/Packages (https://github.com/sublimehq/Packages)
|
||||
22. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell)
|
||||
23. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle)
|
||||
24. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle)
|
||||
25. textmate/coffee-script.tmbundle (https://github.com/textmate/coffee-script.tmbundle)
|
||||
26. textmate/css.tmbundle (https://github.com/textmate/css.tmbundle)
|
||||
27. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle)
|
||||
28. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle)
|
||||
29. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle)
|
||||
30. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle)
|
||||
31. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle)
|
||||
32. textmate/java.tmbundle (https://github.com/textmate/java.tmbundle)
|
||||
33. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle)
|
||||
34. textmate/less.tmbundle (https://github.com/textmate/less.tmbundle)
|
||||
35. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle)
|
||||
36. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle)
|
||||
37. textmate/objective-c.tmbundle (https://github.com/textmate/objective-c.tmbundle)
|
||||
38. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle)
|
||||
39. textmate/php.tmbundle (https://github.com/textmate/php.tmbundle)
|
||||
40. textmate/python.tmbundle (https://github.com/textmate/python.tmbundle)
|
||||
41. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle)
|
||||
42. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle)
|
||||
43. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle)
|
||||
44. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle)
|
||||
45. textmate/xml.tmbundle (https://github.com/textmate/xml.tmbundle)
|
||||
46. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle)
|
||||
47. typescript version 1.5 (https://github.com/Microsoft/TypeScript)
|
||||
48. typescript version 1.8.2 (https://github.com/Microsoft/TypeScript)
|
||||
49. typescript-sublime-plugin version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage)
|
||||
50. unity-shader-files version 0.1.0 (https://github.com/nashella/unity-shader-files)
|
||||
51. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift)
|
||||
1. atom/language-c version 0.51.3 (https://github.com/atom/language-c)
|
||||
2. atom/language-sass version 0.52.0 (https://github.com/atom/language-sass)
|
||||
3. Benvie/JavaScriptNext.tmLanguage (https://github.com/Benvie/JavaScriptNext.tmLanguage)
|
||||
4. chjj-marked version 0.3.2 (https://github.com/npmcomponent/chjj-marked)
|
||||
5. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes)
|
||||
6. davidrios/jade-tmbundle (https://github.com/davidrios/jade-tmbundle)
|
||||
7. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||
8. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift)
|
||||
9. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
|
||||
10. Ionic documentation version 1.2.4 (https://github.com/driftyco/ionic-site)
|
||||
11. ionide/ionide-fsharp (https://github.com/ionide/ionide-fsharp)
|
||||
12. js-beautify version 1.6.2 (https://github.com/beautify-web/js-beautify)
|
||||
13. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert)
|
||||
14. language-docker (https://github.com/docker/docker)
|
||||
15. language-go version 0.39.0 (https://github.com/atom/language-go)
|
||||
16. language-php version 0.29.0 (https://github.com/atom/language-php)
|
||||
17. language-rust version 0.4.4 (https://github.com/zargony/atom-language-rust)
|
||||
18. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage)
|
||||
19. mmcgrana/textmate-clojure (https://github.com/mmcgrana/textmate-clojure)
|
||||
20. octicons-code version 3.1.0 (https://octicons.github.com)
|
||||
21. octicons-font version 3.1.0 (https://octicons.github.com)
|
||||
22. string_scorer version 0.1.20 (https://github.com/joshaven/string_score)
|
||||
23. sublimehq/Packages (https://github.com/sublimehq/Packages)
|
||||
24. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell)
|
||||
25. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle)
|
||||
26. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle)
|
||||
27. textmate/coffee-script.tmbundle (https://github.com/textmate/coffee-script.tmbundle)
|
||||
28. textmate/css.tmbundle (https://github.com/textmate/css.tmbundle)
|
||||
29. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle)
|
||||
30. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle)
|
||||
31. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle)
|
||||
32. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle)
|
||||
33. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle)
|
||||
34. textmate/java.tmbundle (https://github.com/textmate/java.tmbundle)
|
||||
35. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle)
|
||||
36. textmate/less.tmbundle (https://github.com/textmate/less.tmbundle)
|
||||
37. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle)
|
||||
38. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle)
|
||||
39. textmate/objective-c.tmbundle (https://github.com/textmate/objective-c.tmbundle)
|
||||
40. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle)
|
||||
41. textmate/php.tmbundle (https://github.com/textmate/php.tmbundle)
|
||||
42. textmate/python.tmbundle (https://github.com/textmate/python.tmbundle)
|
||||
43. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle)
|
||||
44. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle)
|
||||
45. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle)
|
||||
46. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle)
|
||||
47. textmate/xml.tmbundle (https://github.com/textmate/xml.tmbundle)
|
||||
48. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle)
|
||||
49. typescript version 1.5 (https://github.com/Microsoft/TypeScript)
|
||||
50. typescript version 1.8.2 (https://github.com/Microsoft/TypeScript)
|
||||
51. typescript-sublime-plugin version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage)
|
||||
52. unity-shader-files version 0.1.0 (https://github.com/nashella/unity-shader-files)
|
||||
53. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift)
|
||||
|
||||
|
||||
%% atom/language-c NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
This package was derived from a TextMate bundle located at
|
||||
https://github.com/textmate/c.tmbundle and distributed under the following
|
||||
license, located in `README.mdown`:
|
||||
|
||||
Permission to copy, use, modify, sell and distribute this
|
||||
software is granted. This software is provided "as is" without
|
||||
express or implied warranty, and with no claim as to its
|
||||
suitability for any purpose.
|
||||
=========================================
|
||||
END OF atom/language-c NOTICES AND INFORMATION
|
||||
|
||||
%% atom/language-sass NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
This package was derived from a TextMate bundle located at
|
||||
https://github.com/alexsancho/Sass.tmbundle and distributed under the following
|
||||
license, located in `LICENSE.md`:
|
||||
|
||||
Copyright (c) 2012 Alex Sancho, http://alexsancho.name/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
=========================================
|
||||
END OF atom/language-sass NOTICES AND INFORMATION
|
||||
|
||||
%% Benvie/JavaScriptNext.tmLanguage NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
@ -1096,8 +1187,6 @@ END OF textmate/c.tmbundle NOTICES AND INFORMATION
|
|||
|
||||
%% textmate/coffee-script.tmbundle NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2009-2014 Jeremy Ashkenas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
|
|
|
@ -2,7 +2,7 @@ environment:
|
|||
ATOM_SHELL_INTERNAL_RUN_AS_NODE: 1
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 4.1.1 x64
|
||||
- ps: Install-Product node 5.10.1 x64
|
||||
- npm install -g gulp mocha
|
||||
|
||||
build_script:
|
||||
|
@ -12,3 +12,4 @@ build_script:
|
|||
|
||||
test_script:
|
||||
- .\scripts\test.bat
|
||||
- .\scripts\test-integration.bat
|
||||
|
|
|
@ -15,6 +15,13 @@ var File = require('vinyl');
|
|||
var bundle = require('./lib/bundle');
|
||||
var util = require('./lib/util');
|
||||
var i18n = require('./lib/i18n');
|
||||
var gulpUtil = require('gulp-util');
|
||||
|
||||
var quiet = !!process.env['VSCODE_BUILD_QUIET'];
|
||||
|
||||
function log(prefix, message) {
|
||||
gulpUtil.log(gulpUtil.colors.cyan('[' + prefix + ']'), message);
|
||||
}
|
||||
|
||||
var root = path.dirname(__dirname);
|
||||
var commit = util.getVersion(root);
|
||||
|
@ -22,7 +29,7 @@ var commit = util.getVersion(root);
|
|||
var tsOptions = {
|
||||
target: 'ES5',
|
||||
module: 'amd',
|
||||
verbose: true,
|
||||
verbose: !quiet,
|
||||
preserveConstEnums: true,
|
||||
experimentalDecorators: true,
|
||||
sourceMap: true,
|
||||
|
@ -33,17 +40,11 @@ exports.loaderConfig = function (emptyPaths) {
|
|||
var result = {
|
||||
paths: {
|
||||
'vs': 'out-build/vs',
|
||||
'vs/extensions': 'extensions',
|
||||
'vscode': 'empty:'
|
||||
},
|
||||
'vs/text': {
|
||||
paths: {
|
||||
'vs/extensions': 'extensions'
|
||||
}
|
||||
}
|
||||
nodeModules: emptyPaths||[]
|
||||
};
|
||||
|
||||
(emptyPaths || []).forEach(function(m) { result.paths[m] = 'empty:'; });
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -55,7 +56,6 @@ function loader(bundledFileHeader) {
|
|||
'out-build/vs/loader.js',
|
||||
'out-build/vs/css.js',
|
||||
'out-build/vs/nls.js',
|
||||
'out-build/vs/text.js'
|
||||
], { base: 'out-build' })
|
||||
.pipe(es.through(function(data) {
|
||||
if (isFirst) {
|
||||
|
@ -128,6 +128,7 @@ function toBundleStream(bundledFileHeader, bundles) {
|
|||
* - resources (svg, etc.)
|
||||
* - loaderConfig
|
||||
* - header (basically the Copyright treatment)
|
||||
* - bundleInfo (boolean - emit bundleInfo.json file)
|
||||
* - out (out folder name)
|
||||
*/
|
||||
exports.optimizeTask = function(opts) {
|
||||
|
@ -139,12 +140,33 @@ exports.optimizeTask = function(opts) {
|
|||
var out = opts.out;
|
||||
|
||||
return function() {
|
||||
var bundlesStream = es.through();
|
||||
var bundlesStream = es.through(); // this stream will contain the bundled files
|
||||
var resourcesStream = es.through(); // this stream will contain the resources
|
||||
var bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
|
||||
|
||||
bundle.bundle(entryPoints, loaderConfig, function(err, result) {
|
||||
if (err) { return bundlesStream.emit('error', JSON.stringify(err)); }
|
||||
|
||||
toBundleStream(bundledFileHeader, result).pipe(bundlesStream);
|
||||
toBundleStream(bundledFileHeader, result.files).pipe(bundlesStream);
|
||||
|
||||
// Remove css inlined resources
|
||||
var filteredResources = [];
|
||||
filteredResources = filteredResources.concat(resources);
|
||||
result.cssInlinedResources.forEach(function(resource) {
|
||||
log('optimizer', 'excluding inlined: ' + resource);
|
||||
filteredResources.push('!' + resource);
|
||||
});
|
||||
gulp.src(filteredResources, { base: 'out-build' }).pipe(resourcesStream);
|
||||
|
||||
var bundleInfoArray = [];
|
||||
if (opts.bundleInfo) {
|
||||
bundleInfoArray.push(new File({
|
||||
path: 'bundleInfo.json',
|
||||
base: '.',
|
||||
contents: new Buffer(JSON.stringify(result.bundleData, null, '\t'))
|
||||
}));
|
||||
}
|
||||
es.readArray(bundleInfoArray).pipe(bundleInfoStream);
|
||||
});
|
||||
|
||||
var otherSourcesStream = es.through();
|
||||
|
@ -165,7 +187,8 @@ exports.optimizeTask = function(opts) {
|
|||
loader(bundledFileHeader),
|
||||
bundlesStream,
|
||||
otherSourcesStream,
|
||||
gulp.src(resources, { base: 'out-build' })
|
||||
resourcesStream,
|
||||
bundleInfoStream
|
||||
);
|
||||
|
||||
return result
|
||||
|
@ -174,7 +197,9 @@ exports.optimizeTask = function(opts) {
|
|||
addComment: true,
|
||||
includeContent: true
|
||||
}))
|
||||
.pipe(i18n.processNlsFiles())
|
||||
.pipe(i18n.processNlsFiles({
|
||||
fileHeader: bundledFileHeader
|
||||
}))
|
||||
.pipe(gulp.dest(out));
|
||||
};
|
||||
};
|
||||
|
@ -244,7 +269,7 @@ exports.minifyTask = function (src, addSourceMapsComment) {
|
|||
.pipe(uglifyWithCopyrights())
|
||||
.pipe(jsFilter.restore)
|
||||
.pipe(cssFilter)
|
||||
.pipe(minifyCSS())
|
||||
.pipe(minifyCSS({ reduceIdents: false }))
|
||||
.pipe(cssFilter.restore)
|
||||
.pipe(sourcemaps.write('./', {
|
||||
sourceMappingURL: function (file) {
|
||||
|
|
|
@ -9,16 +9,20 @@ var _ = require('underscore');
|
|||
var buildfile = require('../src/buildfile');
|
||||
var util = require('./lib/util');
|
||||
var common = require('./gulpfile.common');
|
||||
var es = require('event-stream');
|
||||
var fs = require('fs');
|
||||
var File = require('vinyl');
|
||||
|
||||
var root = path.dirname(__dirname);
|
||||
var headerVersion = process.env['BUILD_SOURCEVERSION'] || util.getVersion(root);
|
||||
var sha1 = util.getVersion(root);
|
||||
var semver = require('./monaco/package.json').version;
|
||||
var headerVersion = semver + '(' + sha1 + ')';
|
||||
|
||||
// Build
|
||||
|
||||
var editorEntryPoints = _.flatten([
|
||||
buildfile.entrypoint('vs/editor/editor.main'),
|
||||
buildfile.base,
|
||||
buildfile.standaloneLanguages,
|
||||
buildfile.standaloneLanguages2,
|
||||
buildfile.editor,
|
||||
buildfile.languages
|
||||
|
@ -26,6 +30,9 @@ var editorEntryPoints = _.flatten([
|
|||
|
||||
var editorResources = [
|
||||
'out-build/vs/{base,editor}/**/*.{svg,png}',
|
||||
'!out-build/vs/base/browser/ui/splitview/**/*',
|
||||
'!out-build/vs/base/browser/ui/toolbar/**/*',
|
||||
'!out-build/vs/base/browser/ui/octiconLabel/**/*',
|
||||
'out-build/vs/base/worker/workerMainCompatibility.html',
|
||||
'out-build/vs/base/worker/workerMain.{js,js.map}',
|
||||
'!out-build/vs/workbench/**',
|
||||
|
@ -34,8 +41,7 @@ var editorResources = [
|
|||
|
||||
var editorOtherSources = [
|
||||
'out-build/vs/css.js',
|
||||
'out-build/vs/nls.js',
|
||||
'out-build/vs/text.js'
|
||||
'out-build/vs/nls.js'
|
||||
];
|
||||
|
||||
var BUNDLED_FILE_HEADER = [
|
||||
|
@ -60,6 +66,8 @@ function editorLoaderConfig(removeAllOSS) {
|
|||
result.paths['vs/languages/lib/common/beautify-html'] = 'out-build/vs/languages/lib/common/beautify-html.mock';
|
||||
}
|
||||
|
||||
result['vs/css'] = { inlineResources: true };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -70,9 +78,130 @@ gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-build'], common
|
|||
resources: editorResources,
|
||||
loaderConfig: editorLoaderConfig(false),
|
||||
header: BUNDLED_FILE_HEADER,
|
||||
bundleInfo: true,
|
||||
out: 'out-editor'
|
||||
}));
|
||||
|
||||
gulp.task('clean-minified-editor', util.rimraf('out-editor-min'));
|
||||
gulp.task('minify-editor', ['clean-minified-editor', 'optimize-editor'], common.minifyTask('out-editor', true));
|
||||
gulp.task('editor-distro', ['minify-editor', 'optimize-editor']);
|
||||
|
||||
gulp.task('clean-editor-distro', util.rimraf('out-monaco-editor-core'));
|
||||
gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-editor'], function() {
|
||||
return es.merge(
|
||||
// other assets
|
||||
es.merge(
|
||||
gulp.src('build/monaco/LICENSE'),
|
||||
gulp.src('build/monaco/ThirdPartyNotices.txt'),
|
||||
gulp.src('src/vs/monaco.d.ts')
|
||||
).pipe(gulp.dest('out-monaco-editor-core')),
|
||||
|
||||
// package.json
|
||||
gulp.src('build/monaco/package.json')
|
||||
.pipe(es.through(function(data) {
|
||||
var json = JSON.parse(data.contents.toString());
|
||||
json.private = false;
|
||||
data.contents = new Buffer(JSON.stringify(json, null, ' '));
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(gulp.dest('out-monaco-editor-core')),
|
||||
|
||||
// README.md
|
||||
gulp.src('build/monaco/README-npm.md')
|
||||
.pipe(es.through(function(data) {
|
||||
this.emit('data', new File({
|
||||
path: data.path.replace(/README-npm\.md/, 'README.md'),
|
||||
base: data.base,
|
||||
contents: data.contents
|
||||
}));
|
||||
}))
|
||||
.pipe(gulp.dest('out-monaco-editor-core')),
|
||||
|
||||
// dev folder
|
||||
es.merge(
|
||||
gulp.src('out-editor/**/*')
|
||||
).pipe(gulp.dest('out-monaco-editor-core/dev')),
|
||||
|
||||
// min folder
|
||||
es.merge(
|
||||
gulp.src('out-editor-min/**/*')
|
||||
).pipe(filterStream(function(path) {
|
||||
// no map files
|
||||
return !/(\.js\.map$)|(nls\.metadata\.json$)|(bundleInfo\.json$)/.test(path);
|
||||
})).pipe(es.through(function(data) {
|
||||
// tweak the sourceMappingURL
|
||||
if (!/\.js$/.test(data.path)) {
|
||||
this.emit('data', data);
|
||||
return;
|
||||
}
|
||||
|
||||
var relativePathToMap = path.relative(path.join(data.relative), path.join('min-maps', data.relative + '.map'));
|
||||
|
||||
var strContents = data.contents.toString();
|
||||
var newStr = '//# sourceMappingURL=' + relativePathToMap.replace(/\\/g, '/');
|
||||
strContents = strContents.replace(/\/\/\# sourceMappingURL=[^ ]+$/, newStr);
|
||||
|
||||
data.contents = new Buffer(strContents);
|
||||
this.emit('data', data);
|
||||
})).pipe(gulp.dest('out-monaco-editor-core/min')),
|
||||
|
||||
// min-maps folder
|
||||
es.merge(
|
||||
gulp.src('out-editor-min/**/*')
|
||||
).pipe(filterStream(function(path) {
|
||||
// no map files
|
||||
return /\.js\.map$/.test(path);
|
||||
})).pipe(gulp.dest('out-monaco-editor-core/min-maps'))
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('analyze-editor-distro', function() {
|
||||
var bundleInfo = require('../out-editor/bundleInfo.json');
|
||||
var graph = bundleInfo.graph;
|
||||
var bundles = bundleInfo.bundles;
|
||||
|
||||
var inverseGraph = {};
|
||||
Object.keys(graph).forEach(function(module) {
|
||||
var dependencies = graph[module];
|
||||
dependencies.forEach(function(dep) {
|
||||
inverseGraph[dep] = inverseGraph[dep] || [];
|
||||
inverseGraph[dep].push(module);
|
||||
});
|
||||
});
|
||||
|
||||
var detailed = {};
|
||||
Object.keys(bundles).forEach(function(entryPoint) {
|
||||
var included = bundles[entryPoint];
|
||||
var includedMap = {};
|
||||
included.forEach(function(included) {
|
||||
includedMap[included] = true;
|
||||
});
|
||||
|
||||
var explanation = [];
|
||||
included.map(function(included) {
|
||||
if (included.indexOf('!') >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reason = (inverseGraph[included]||[]).filter(function(mod) {
|
||||
return !!includedMap[mod];
|
||||
});
|
||||
explanation.push({
|
||||
module: included,
|
||||
reason: reason
|
||||
});
|
||||
});
|
||||
|
||||
detailed[entryPoint] = explanation;
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(detailed, null, '\t'));
|
||||
});
|
||||
|
||||
function filterStream(testFunc) {
|
||||
return es.through(function(data) {
|
||||
if (!testFunc(data.relative)) {
|
||||
return;
|
||||
}
|
||||
this.emit('data', data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,13 +31,11 @@ var languages = ['chs', 'cht', 'jpn', 'kor', 'deu', 'fra', 'esn', 'rus', 'ita'];
|
|||
|
||||
var tasks = compilations.map(function(tsconfigFile) {
|
||||
var absolutePath = path.join(extensionsPath, tsconfigFile);
|
||||
var absoluteDirname = path.dirname(absolutePath);
|
||||
var relativeDirname = path.dirname(tsconfigFile);
|
||||
|
||||
var tsOptions = require(absolutePath).compilerOptions;
|
||||
tsOptions.verbose = !quiet;
|
||||
tsOptions.sourceMap = true;
|
||||
tsOptions.sourceRoot = util.toFileUri(path.join(absoluteDirname, 'src'));
|
||||
|
||||
var name = relativeDirname.replace(/\//g, '-');
|
||||
|
||||
|
@ -74,7 +72,10 @@ var tasks = compilations.map(function(tsconfigFile) {
|
|||
.pipe(sourcemaps.write('.', {
|
||||
addComment: false,
|
||||
includeContent: !!build,
|
||||
sourceRoot: tsOptions.sourceRoot
|
||||
sourceRoot: function(file) {
|
||||
var levels = file.relative.split(path.sep).length;
|
||||
return '../'.repeat(levels) + 'src';
|
||||
}
|
||||
}))
|
||||
.pipe(tsFilter.restore)
|
||||
.pipe(build ? nlsDev.createAdditionalLanguageFiles(languages, i18n, out) : es.through())
|
||||
|
|
|
@ -26,7 +26,8 @@ var eolFilter = [
|
|||
'!**/node_modules/**',
|
||||
'!**/fixtures/**',
|
||||
'!**/*.{svg,exe,png,scpt,bat,cmd,cur,ttf,woff,eot}',
|
||||
'!build/{lib,tslintRules}/**/*.js'
|
||||
'!build/{lib,tslintRules}/**/*.js',
|
||||
'!build/monaco/**/*'
|
||||
];
|
||||
|
||||
var indentationFilter = [
|
||||
|
@ -37,6 +38,7 @@ var indentationFilter = [
|
|||
'!**/*.yml',
|
||||
'!**/lib/**',
|
||||
'!**/*.d.ts',
|
||||
'!**/*.d.ts.recipe',
|
||||
'!extensions/typescript/server/**',
|
||||
'!test/assert.js',
|
||||
'!**/package.json',
|
||||
|
@ -48,14 +50,14 @@ var indentationFilter = [
|
|||
'!**/vs/base/common/marked/raw.marked.js',
|
||||
'!**/vs/base/common/winjs.base.raw.js',
|
||||
'!**/vs/base/node/terminateProcess.sh',
|
||||
'!**/vs/text.js',
|
||||
'!**/vs/nls.js',
|
||||
'!**/vs/css.js',
|
||||
'!**/vs/loader.js',
|
||||
'!extensions/**/snippets/**',
|
||||
'!extensions/**/syntaxes/**',
|
||||
'!extensions/**/themes/**',
|
||||
'!extensions/**/colorize-fixtures/**'
|
||||
'!extensions/**/colorize-fixtures/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace/**'
|
||||
];
|
||||
|
||||
var copyrightFilter = [
|
||||
|
@ -69,10 +71,10 @@ var copyrightFilter = [
|
|||
'!**/*.bat',
|
||||
'!**/*.cmd',
|
||||
'!resources/win32/bin/code.js',
|
||||
'!**/*.xml',
|
||||
'!**/*.sh',
|
||||
'!**/*.txt',
|
||||
'!**/*.xpm',
|
||||
'!src/vs/editor/standalone-languages/swift.ts',
|
||||
];
|
||||
|
||||
var tslintFilter = [
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
var gulp = require('gulp');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
var es = require('event-stream');
|
||||
var azure = require('gulp-azure-storage');
|
||||
var electron = require('gulp-atom-electron');
|
||||
|
@ -19,6 +18,7 @@ var remote = require('gulp-remote-src');
|
|||
var shell = require("gulp-shell");
|
||||
var _ = require('underscore');
|
||||
var packageJson = require('../package.json');
|
||||
var shrinkwrap = require('../npm-shrinkwrap.json');
|
||||
var util = require('./lib/util');
|
||||
var buildfile = require('../src/buildfile');
|
||||
var common = require('./gulpfile.common');
|
||||
|
@ -31,8 +31,8 @@ var baseModules = [
|
|||
'applicationinsights', 'assert', 'child_process', 'chokidar', 'crypto', 'emmet',
|
||||
'events', 'fs', 'getmac', 'glob', 'graceful-fs', 'http', 'http-proxy-agent',
|
||||
'https', 'https-proxy-agent', 'iconv-lite', 'electron', 'net',
|
||||
'os', 'path', 'readline', 'sax', 'semver', 'stream', 'string_decoder', 'url',
|
||||
'vscode-textmate', 'winreg', 'yauzl', 'native-keymap', 'zlib'
|
||||
'os', 'path', 'pty.js', 'readline', 'sax', 'semver', 'stream', 'string_decoder', 'url', 'term.js',
|
||||
'vscode-textmate', 'winreg', 'yauzl', 'native-keymap', 'zlib', 'minimist', 'xterm'
|
||||
];
|
||||
|
||||
// Build
|
||||
|
@ -42,7 +42,8 @@ var vscodeEntryPoints = _.flatten([
|
|||
buildfile.base,
|
||||
buildfile.editor,
|
||||
buildfile.languages,
|
||||
buildfile.vscode
|
||||
buildfile.workbench,
|
||||
buildfile.code
|
||||
]);
|
||||
|
||||
var vscodeResources = [
|
||||
|
@ -50,6 +51,7 @@ var vscodeResources = [
|
|||
'out-build/cli.js',
|
||||
'out-build/bootstrap.js',
|
||||
'out-build/bootstrap-amd.js',
|
||||
'out-build/paths.js',
|
||||
'out-build/vs/**/*.{svg,png,cur}',
|
||||
'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh}',
|
||||
'out-build/vs/base/worker/workerMainCompatibility.html',
|
||||
|
@ -62,8 +64,10 @@ var vscodeResources = [
|
|||
'out-build/vs/workbench/parts/execution/**/*.scpt',
|
||||
'out-build/vs/workbench/parts/git/**/*.html',
|
||||
'out-build/vs/workbench/parts/git/**/*.sh',
|
||||
'out-build/vs/workbench/parts/html/browser/webview.html',
|
||||
'out-build/vs/workbench/parts/markdown/**/*.md',
|
||||
'out-build/vs/workbench/parts/tasks/**/*.json',
|
||||
'out-build/vs/workbench/parts/terminal/electron-browser/terminalProcess.js',
|
||||
'out-build/vs/workbench/services/files/**/*.exe',
|
||||
'out-build/vs/workbench/services/files/**/*.md',
|
||||
'!**/test/**'
|
||||
|
@ -91,12 +95,13 @@ gulp.task('minify-vscode', ['clean-minified-vscode', 'optimize-vscode'], common.
|
|||
// Package
|
||||
var product = require('../product.json');
|
||||
var darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8'));
|
||||
var linuxPackageRevision = getEpochTime();
|
||||
|
||||
var config = {
|
||||
version: packageJson.electronVersion,
|
||||
productAppName: product.nameLong,
|
||||
companyName: 'Microsoft Corporation',
|
||||
copyright: 'Copyright (C) 2015 Microsoft. All rights reserved',
|
||||
copyright: 'Copyright (C) 2016 Microsoft. All rights reserved',
|
||||
darwinIcon: 'resources/darwin/code.icns',
|
||||
darwinBundleIdentifier: product.darwinBundleIdentifier,
|
||||
darwinApplicationCategoryType: 'public.app-category.developer-tools',
|
||||
|
@ -163,14 +168,15 @@ function packageTask(platform, arch, opts) {
|
|||
'!extensions/*/src/**',
|
||||
'!extensions/*/out/**/test/**',
|
||||
'!extensions/*/test/**',
|
||||
'!extensions/*/{client,server}/src/**',
|
||||
'!extensions/*/{client,server}/test/**',
|
||||
'!extensions/*/{client,server}/out/**/test/**',
|
||||
'!extensions/*/{client,server}/out/**/typings/**',
|
||||
'!extensions/**/.vscode/**',
|
||||
'!extensions/typescript/bin/**',
|
||||
'!extensions/vscode-api-tests/**',
|
||||
'!extensions/vscode-colorize-tests/**',
|
||||
'!extensions/json/server/.vscode/**',
|
||||
'!extensions/json/server/src/**',
|
||||
'!extensions/json/server/out/**/test/**',
|
||||
'!extensions/json/server/test/**',
|
||||
'!extensions/json/server/typings/**'
|
||||
'!extensions/css/server/out/data/**'
|
||||
], { base: '.' });
|
||||
|
||||
var sources = es.merge(src, extensions)
|
||||
|
@ -193,15 +199,15 @@ function packageTask(platform, arch, opts) {
|
|||
var license = gulp.src(['Credits_*', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.' });
|
||||
var api = gulp.src('src/vs/vscode.d.ts').pipe(rename('out/vs/vscode.d.ts'));
|
||||
|
||||
var depsSrc = _.flatten(Object.keys(packageJson.dependencies).concat(Object.keys(packageJson.optionalDependencies))
|
||||
.map(function (d) { return ['node_modules/' + d + '/**', '!node_modules/' + d + '/**/{test,tests}/**']; })
|
||||
);
|
||||
var depsSrc = _.flatten(Object.keys(shrinkwrap.dependencies)
|
||||
.map(function (d) { return ['node_modules/' + d + '/**', '!node_modules/' + d + '/**/{test,tests}/**']; }));
|
||||
|
||||
var deps = gulp.src(depsSrc, { base: '.', dot: true })
|
||||
.pipe(util.cleanNodeModule('fsevents', ['binding.gyp', 'fsevents.cc', 'build/**', 'src/**', 'test/**'], true))
|
||||
.pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], true))
|
||||
.pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], true))
|
||||
.pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], true));
|
||||
.pipe(util.cleanNodeModule('fsevents', ['binding.gyp', 'fsevents.cc', 'build/**', 'src/**', 'test/**'], ['**/*.node']))
|
||||
.pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node']))
|
||||
.pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
|
||||
.pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node']))
|
||||
.pipe(util.cleanNodeModule('pty.js', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/**']));
|
||||
|
||||
var all = es.merge(
|
||||
api,
|
||||
|
@ -260,12 +266,12 @@ function getEpochTime() {
|
|||
function prepareDebPackage(arch) {
|
||||
var binaryDir = '../VSCode-linux-' + arch;
|
||||
var debArch = getDebPackageArch(arch);
|
||||
var destination = '.build/linux/deb/' + debArch + '/vscode-' + debArch;
|
||||
var packageRevision = getEpochTime();
|
||||
var destination = '.build/linux/deb/' + debArch + '/' + product.applicationName + '-' + debArch;
|
||||
|
||||
return function () {
|
||||
var desktop = gulp.src('resources/linux/code.desktop', { base: '.' })
|
||||
.pipe(replace('@@NAME_LONG@@', product.nameLong))
|
||||
.pipe(replace('@@NAME_SHORT@@', product.nameShort))
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(rename('usr/share/applications/' + product.applicationName + '.desktop'));
|
||||
|
||||
|
@ -282,7 +288,7 @@ function prepareDebPackage(arch) {
|
|||
var that = this;
|
||||
gulp.src('resources/linux/debian/control.template', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version + '-' + packageRevision))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision))
|
||||
.pipe(replace('@@ARCHITECTURE@@', debArch))
|
||||
.pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024)))
|
||||
.pipe(rename('DEBIAN/control'))
|
||||
|
@ -293,6 +299,10 @@ function prepareDebPackage(arch) {
|
|||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(rename('DEBIAN/prerm'))
|
||||
|
||||
var postrm = gulp.src('resources/linux/debian/postrm.template', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(rename('DEBIAN/postrm'))
|
||||
|
||||
var postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@ARCHITECTURE@@', debArch))
|
||||
|
@ -300,7 +310,7 @@ function prepareDebPackage(arch) {
|
|||
.pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@'))
|
||||
.pipe(rename('DEBIAN/postinst'))
|
||||
|
||||
var all = es.merge(control, postinst, prerm, desktop, icon, code);
|
||||
var all = es.merge(control, postinst, postrm, prerm, desktop, icon, code);
|
||||
|
||||
return all.pipe(symdest(destination));
|
||||
};
|
||||
|
@ -309,9 +319,9 @@ function prepareDebPackage(arch) {
|
|||
function buildDebPackage(arch) {
|
||||
var debArch = getDebPackageArch(arch);
|
||||
return shell.task([
|
||||
'chmod 755 vscode-' + debArch + '/DEBIAN/postinst ' + 'vscode-' + debArch + '/DEBIAN/prerm',
|
||||
'chmod 755 ' + product.applicationName + '-' + debArch + '/DEBIAN/postinst ' + product.applicationName + '-' + debArch + '/DEBIAN/prerm',
|
||||
'mkdir -p deb',
|
||||
'fakeroot dpkg-deb -b vscode-' + debArch + ' deb/vscode-' + debArch + '.deb',
|
||||
'fakeroot dpkg-deb -b ' + product.applicationName + '-' + debArch + ' deb',
|
||||
'dpkg-scanpackages deb /dev/null > Packages'
|
||||
], { cwd: '.build/linux/deb/' + debArch});
|
||||
}
|
||||
|
@ -327,11 +337,11 @@ function getRpmPackageArch(arch) {
|
|||
function prepareRpmPackage(arch) {
|
||||
var binaryDir = '../VSCode-linux-' + arch;
|
||||
var rpmArch = getRpmPackageArch(arch);
|
||||
var packageRevision = getEpochTime();
|
||||
|
||||
return function () {
|
||||
var desktop = gulp.src('resources/linux/code.desktop', { base: '.' })
|
||||
.pipe(replace('@@NAME_LONG@@', product.nameLong))
|
||||
.pipe(replace('@@NAME_SHORT@@', product.nameShort))
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(rename('BUILD/usr/share/applications/' + product.applicationName + '.desktop'));
|
||||
|
||||
|
@ -345,7 +355,7 @@ function prepareRpmPackage(arch) {
|
|||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@NAME_LONG@@', product.nameLong))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version))
|
||||
.pipe(replace('@@RELEASE@@', packageRevision))
|
||||
.pipe(replace('@@RELEASE@@', linuxPackageRevision))
|
||||
.pipe(replace('@@ARCHITECTURE@@', rpmArch))
|
||||
.pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@'))
|
||||
.pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@'))
|
||||
|
@ -368,7 +378,7 @@ function buildRpmPackage(arch) {
|
|||
return shell.task([
|
||||
'mkdir -p ' + destination,
|
||||
'HOME="$(pwd)/' + destination + '" fakeroot rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch,
|
||||
'cp "' + rpmOut + '/$(ls ' + rpmOut + ')" ' + destination + '/vscode-' + rpmArch + '.rpm',
|
||||
'cp "' + rpmOut + '/$(ls ' + rpmOut + ')" ' + destination + '/',
|
||||
'createrepo ' + destination
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,261 +1,420 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
"use strict";
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var vm = require('vm');
|
||||
/**
|
||||
* Bundle `entryPoints` given config `config`.
|
||||
*/
|
||||
function bundle(entryPoints, config, callback) {
|
||||
var entryPointsMap = {};
|
||||
entryPoints.forEach(function (module) {
|
||||
entryPointsMap[module.name] = module;
|
||||
});
|
||||
var code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js'));
|
||||
var r = vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});');
|
||||
var loaderModule = { exports: {} };
|
||||
r.call({}, require, loaderModule, loaderModule.exports);
|
||||
var loader = loaderModule.exports;
|
||||
config.isBuild = true;
|
||||
loader.config(config);
|
||||
loader(Object.keys(entryPointsMap), function () {
|
||||
var modules = loader.getBuildInfo();
|
||||
callback(null, emitEntryPoints(modules, entryPointsMap));
|
||||
}, function (err) { return callback(err, null); });
|
||||
}
|
||||
exports.bundle = bundle;
|
||||
function emitEntryPoints(modules, entryPoints) {
|
||||
var modulesMap = {};
|
||||
modules.forEach(function (m) {
|
||||
modulesMap[m.id] = m;
|
||||
});
|
||||
var modulesGraph = {};
|
||||
modules.forEach(function (m) {
|
||||
modulesGraph[m.id] = m.dependencies;
|
||||
});
|
||||
var sortedModules = topologicalSort(modulesGraph);
|
||||
var result = [];
|
||||
var usedPlugins = {};
|
||||
Object.keys(entryPoints).forEach(function (moduleToBundle) {
|
||||
var info = entryPoints[moduleToBundle];
|
||||
var rootNodes = [moduleToBundle].concat(info.include || []);
|
||||
var allDependencies = visit(rootNodes, modulesGraph);
|
||||
var excludes = ['require', 'exports', 'module'].concat(info.exclude || []);
|
||||
excludes.forEach(function (excludeRoot) {
|
||||
var allExcludes = visit([excludeRoot], modulesGraph);
|
||||
Object.keys(allExcludes).forEach(function (exclude) {
|
||||
delete allDependencies[exclude];
|
||||
});
|
||||
});
|
||||
var includedModules = sortedModules.filter(function (module) {
|
||||
return allDependencies[module];
|
||||
});
|
||||
var res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules);
|
||||
result = result.concat(res.files);
|
||||
for (var pluginName in res.usedPlugins) {
|
||||
usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName];
|
||||
}
|
||||
});
|
||||
Object.keys(usedPlugins).forEach(function (pluginName) {
|
||||
var plugin = usedPlugins[pluginName];
|
||||
if (typeof plugin.finishBuild === 'function') {
|
||||
var write = function (filename, contents) {
|
||||
result.push({
|
||||
dest: filename,
|
||||
sources: [{
|
||||
path: null,
|
||||
contents: contents
|
||||
}]
|
||||
});
|
||||
};
|
||||
plugin.finishBuild(write);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function emitEntryPoint(modulesMap, deps, entryPoint, includedModules) {
|
||||
var mainResult = {
|
||||
sources: [],
|
||||
dest: entryPoint + '.js'
|
||||
}, results = [mainResult];
|
||||
var usedPlugins = {};
|
||||
var getLoaderPlugin = function (pluginName) {
|
||||
if (!usedPlugins[pluginName]) {
|
||||
usedPlugins[pluginName] = modulesMap[pluginName].exports;
|
||||
}
|
||||
return usedPlugins[pluginName];
|
||||
};
|
||||
includedModules.forEach(function (c) {
|
||||
var bangIndex = c.indexOf('!');
|
||||
if (bangIndex >= 0) {
|
||||
var pluginName = c.substr(0, bangIndex);
|
||||
var plugin = getLoaderPlugin(pluginName);
|
||||
mainResult.sources.push(emitPlugin(entryPoint, plugin, pluginName, c.substr(bangIndex + 1)));
|
||||
return;
|
||||
}
|
||||
var module = modulesMap[c];
|
||||
if (module.path === 'empty:') {
|
||||
return;
|
||||
}
|
||||
var contents = readFileAndRemoveBOM(module.path);
|
||||
if (module.shim) {
|
||||
mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents));
|
||||
}
|
||||
else {
|
||||
mainResult.sources.push(emitNamedModule(c, deps[c], module.defineLocation, module.path, contents));
|
||||
}
|
||||
});
|
||||
Object.keys(usedPlugins).forEach(function (pluginName) {
|
||||
var plugin = usedPlugins[pluginName];
|
||||
if (typeof plugin.writeFile === 'function') {
|
||||
var req = (function () {
|
||||
throw new Error('no-no!');
|
||||
});
|
||||
req.toUrl = function (something) { return something; };
|
||||
var write = function (filename, contents) {
|
||||
results.push({
|
||||
dest: filename,
|
||||
sources: [{
|
||||
path: null,
|
||||
contents: contents
|
||||
}]
|
||||
});
|
||||
};
|
||||
plugin.writeFile(pluginName, entryPoint, req, write, {});
|
||||
}
|
||||
});
|
||||
return {
|
||||
files: results,
|
||||
usedPlugins: usedPlugins
|
||||
};
|
||||
}
|
||||
function readFileAndRemoveBOM(path) {
|
||||
var BOM_CHAR_CODE = 65279;
|
||||
var contents = fs.readFileSync(path, 'utf8');
|
||||
// Remove BOM
|
||||
if (contents.charCodeAt(0) === BOM_CHAR_CODE) {
|
||||
contents = contents.substring(1);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
function emitPlugin(entryPoint, plugin, pluginName, moduleName) {
|
||||
var result = '';
|
||||
if (typeof plugin.write === 'function') {
|
||||
var write = (function (what) {
|
||||
result += what;
|
||||
});
|
||||
write.getEntryPoint = function () {
|
||||
return entryPoint;
|
||||
};
|
||||
write.asModule = function (moduleId, code) {
|
||||
code = code.replace(/^define\(/, 'define("' + moduleId + '",');
|
||||
result += code;
|
||||
};
|
||||
plugin.write(pluginName, moduleName, write);
|
||||
}
|
||||
return {
|
||||
path: null,
|
||||
contents: result
|
||||
};
|
||||
}
|
||||
function emitNamedModule(moduleId, myDeps, defineCallPosition, path, contents) {
|
||||
// `defineCallPosition` is the position in code: |define()
|
||||
var defineCallOffset = positionToOffset(contents, defineCallPosition.line, defineCallPosition.col);
|
||||
// `parensOffset` is the position in code: define|()
|
||||
var parensOffset = contents.indexOf('(', defineCallOffset);
|
||||
var insertStr = '"' + moduleId + '", ';
|
||||
return {
|
||||
path: path,
|
||||
contents: contents.substr(0, parensOffset + 1) + insertStr + contents.substr(parensOffset + 1)
|
||||
};
|
||||
}
|
||||
function emitShimmedModule(moduleId, myDeps, factory, path, contents) {
|
||||
var strDeps = (myDeps.length > 0 ? '"' + myDeps.join('", "') + '"' : '');
|
||||
var strDefine = 'define("' + moduleId + '", [' + strDeps + '], ' + factory + ');';
|
||||
return {
|
||||
path: path,
|
||||
contents: contents + '\n;\n' + strDefine
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert a position (line:col) to (offset) in string `str`
|
||||
*/
|
||||
function positionToOffset(str, desiredLine, desiredCol) {
|
||||
if (desiredLine === 1) {
|
||||
return desiredCol - 1;
|
||||
}
|
||||
var line = 1, lastNewLineOffset = -1;
|
||||
do {
|
||||
if (desiredLine === line) {
|
||||
return lastNewLineOffset + 1 + desiredCol - 1;
|
||||
}
|
||||
lastNewLineOffset = str.indexOf('\n', lastNewLineOffset + 1);
|
||||
line++;
|
||||
} while (lastNewLineOffset >= 0);
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Return a set of reachable nodes in `graph` starting from `rootNodes`
|
||||
*/
|
||||
function visit(rootNodes, graph) {
|
||||
var result = {}, queue = rootNodes;
|
||||
rootNodes.forEach(function (node) {
|
||||
result[node] = true;
|
||||
});
|
||||
while (queue.length > 0) {
|
||||
var el = queue.shift();
|
||||
var myEdges = graph[el] || [];
|
||||
myEdges.forEach(function (toNode) {
|
||||
if (!result[toNode]) {
|
||||
result[toNode] = true;
|
||||
queue.push(toNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Perform a topological sort on `graph`
|
||||
*/
|
||||
function topologicalSort(graph) {
|
||||
var allNodes = {}, outgoingEdgeCount = {}, inverseEdges = {};
|
||||
Object.keys(graph).forEach(function (fromNode) {
|
||||
allNodes[fromNode] = true;
|
||||
outgoingEdgeCount[fromNode] = graph[fromNode].length;
|
||||
graph[fromNode].forEach(function (toNode) {
|
||||
allNodes[toNode] = true;
|
||||
outgoingEdgeCount[toNode] = outgoingEdgeCount[toNode] || 0;
|
||||
inverseEdges[toNode] = inverseEdges[toNode] || [];
|
||||
inverseEdges[toNode].push(fromNode);
|
||||
});
|
||||
});
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting
|
||||
var S = [], L = [];
|
||||
Object.keys(allNodes).forEach(function (node) {
|
||||
if (outgoingEdgeCount[node] === 0) {
|
||||
delete outgoingEdgeCount[node];
|
||||
S.push(node);
|
||||
}
|
||||
});
|
||||
while (S.length > 0) {
|
||||
// Ensure the exact same order all the time with the same inputs
|
||||
S.sort();
|
||||
var n = S.shift();
|
||||
L.push(n);
|
||||
var myInverseEdges = inverseEdges[n] || [];
|
||||
myInverseEdges.forEach(function (m) {
|
||||
outgoingEdgeCount[m]--;
|
||||
if (outgoingEdgeCount[m] === 0) {
|
||||
delete outgoingEdgeCount[m];
|
||||
S.push(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Object.keys(outgoingEdgeCount).length > 0) {
|
||||
throw new Error('Cannot do topological sort on cyclic graph, remaining nodes: ' + Object.keys(outgoingEdgeCount));
|
||||
}
|
||||
return L;
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
"use strict";
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var vm = require('vm');
|
||||
/**
|
||||
* Bundle `entryPoints` given config `config`.
|
||||
*/
|
||||
function bundle(entryPoints, config, callback) {
|
||||
var entryPointsMap = {};
|
||||
entryPoints.forEach(function (module) {
|
||||
entryPointsMap[module.name] = module;
|
||||
});
|
||||
var allMentionedModulesMap = {};
|
||||
entryPoints.forEach(function (module) {
|
||||
allMentionedModulesMap[module.name] = true;
|
||||
(module.include || []).forEach(function (includedModule) {
|
||||
allMentionedModulesMap[includedModule] = true;
|
||||
});
|
||||
(module.exclude || []).forEach(function (excludedModule) {
|
||||
allMentionedModulesMap[excludedModule] = true;
|
||||
});
|
||||
});
|
||||
var code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js'));
|
||||
var r = vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});');
|
||||
var loaderModule = { exports: {} };
|
||||
r.call({}, require, loaderModule, loaderModule.exports);
|
||||
var loader = loaderModule.exports;
|
||||
config.isBuild = true;
|
||||
loader.config(config);
|
||||
loader(Object.keys(allMentionedModulesMap), function () {
|
||||
var modules = loader.getBuildInfo();
|
||||
var partialResult = emitEntryPoints(modules, entryPointsMap);
|
||||
var cssInlinedResources = loader('vs/css').getInlinedResources();
|
||||
callback(null, {
|
||||
files: partialResult.files,
|
||||
cssInlinedResources: cssInlinedResources,
|
||||
bundleData: partialResult.bundleData
|
||||
});
|
||||
}, function (err) { return callback(err, null); });
|
||||
}
|
||||
exports.bundle = bundle;
|
||||
function emitEntryPoints(modules, entryPoints) {
|
||||
var modulesMap = {};
|
||||
modules.forEach(function (m) {
|
||||
modulesMap[m.id] = m;
|
||||
});
|
||||
var modulesGraph = {};
|
||||
modules.forEach(function (m) {
|
||||
modulesGraph[m.id] = m.dependencies;
|
||||
});
|
||||
var sortedModules = topologicalSort(modulesGraph);
|
||||
var result = [];
|
||||
var usedPlugins = {};
|
||||
var bundleData = {
|
||||
graph: modulesGraph,
|
||||
bundles: {}
|
||||
};
|
||||
Object.keys(entryPoints).forEach(function (moduleToBundle) {
|
||||
var info = entryPoints[moduleToBundle];
|
||||
var rootNodes = [moduleToBundle].concat(info.include || []);
|
||||
var allDependencies = visit(rootNodes, modulesGraph);
|
||||
var excludes = ['require', 'exports', 'module'].concat(info.exclude || []);
|
||||
excludes.forEach(function (excludeRoot) {
|
||||
var allExcludes = visit([excludeRoot], modulesGraph);
|
||||
Object.keys(allExcludes).forEach(function (exclude) {
|
||||
delete allDependencies[exclude];
|
||||
});
|
||||
});
|
||||
var includedModules = sortedModules.filter(function (module) {
|
||||
return allDependencies[module];
|
||||
});
|
||||
bundleData.bundles[moduleToBundle] = includedModules;
|
||||
var res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules);
|
||||
result = result.concat(res.files);
|
||||
for (var pluginName in res.usedPlugins) {
|
||||
usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName];
|
||||
}
|
||||
});
|
||||
Object.keys(usedPlugins).forEach(function (pluginName) {
|
||||
var plugin = usedPlugins[pluginName];
|
||||
if (typeof plugin.finishBuild === 'function') {
|
||||
var write = function (filename, contents) {
|
||||
result.push({
|
||||
dest: filename,
|
||||
sources: [{
|
||||
path: null,
|
||||
contents: contents
|
||||
}]
|
||||
});
|
||||
};
|
||||
plugin.finishBuild(write);
|
||||
}
|
||||
});
|
||||
return {
|
||||
files: extractStrings(removeDuplicateTSBoilerplate(result)),
|
||||
bundleData: bundleData
|
||||
};
|
||||
}
|
||||
function extractStrings(destFiles) {
|
||||
var parseDefineCall = function (moduleMatch, depsMatch) {
|
||||
var module = moduleMatch.replace(/^"|"$/g, '');
|
||||
var deps = depsMatch.split(',');
|
||||
deps = deps.map(function (dep) {
|
||||
dep = dep.trim();
|
||||
dep = dep.replace(/^"|"$/g, '');
|
||||
dep = dep.replace(/^'|'$/g, '');
|
||||
var prefix = null;
|
||||
var _path = null;
|
||||
var pieces = dep.split('!');
|
||||
if (pieces.length > 1) {
|
||||
prefix = pieces[0] + '!';
|
||||
_path = pieces[1];
|
||||
}
|
||||
else {
|
||||
prefix = '';
|
||||
_path = pieces[0];
|
||||
}
|
||||
if (/^\.\//.test(_path) || /^\.\.\//.test(_path)) {
|
||||
var res = path.join(path.dirname(module), _path).replace(/\\/g, '/');
|
||||
return prefix + res;
|
||||
}
|
||||
return prefix + _path;
|
||||
});
|
||||
return {
|
||||
module: module,
|
||||
deps: deps
|
||||
};
|
||||
};
|
||||
destFiles.forEach(function (destFile, index) {
|
||||
if (!/\.js$/.test(destFile.dest)) {
|
||||
return;
|
||||
}
|
||||
if (/\.nls\.js$/.test(destFile.dest)) {
|
||||
return;
|
||||
}
|
||||
// Do one pass to record the usage counts for each module id
|
||||
var useCounts = {};
|
||||
destFile.sources.forEach(function (source) {
|
||||
var matches = source.contents.match(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
var defineCall = parseDefineCall(matches[1], matches[2]);
|
||||
useCounts[defineCall.module] = (useCounts[defineCall.module] || 0) + 1;
|
||||
defineCall.deps.forEach(function (dep) {
|
||||
useCounts[dep] = (useCounts[dep] || 0) + 1;
|
||||
});
|
||||
});
|
||||
var sortedByUseModules = Object.keys(useCounts);
|
||||
sortedByUseModules.sort(function (a, b) {
|
||||
return useCounts[b] - useCounts[a];
|
||||
});
|
||||
var replacementMap = {};
|
||||
sortedByUseModules.forEach(function (module, index) {
|
||||
replacementMap[module] = index;
|
||||
});
|
||||
destFile.sources.forEach(function (source) {
|
||||
source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, function (_, moduleMatch, depsMatch) {
|
||||
var defineCall = parseDefineCall(moduleMatch, depsMatch);
|
||||
return "define(__m[" + replacementMap[defineCall.module] + "], __M([" + defineCall.deps.map(function (dep) { return replacementMap[dep]; }).join(',') + "])";
|
||||
});
|
||||
});
|
||||
destFile.sources.unshift({
|
||||
path: null,
|
||||
contents: [
|
||||
'(function() {',
|
||||
("var __m = " + JSON.stringify(sortedByUseModules) + ";"),
|
||||
"var __M = function(deps) {",
|
||||
" var result = [];",
|
||||
" for (var i = 0, len = deps.length; i < len; i++) {",
|
||||
" result[i] = __m[deps[i]];",
|
||||
" }",
|
||||
" return result;",
|
||||
"};"
|
||||
].join('\n')
|
||||
});
|
||||
destFile.sources.push({
|
||||
path: null,
|
||||
contents: '}).call(this);'
|
||||
});
|
||||
});
|
||||
return destFiles;
|
||||
}
|
||||
function removeDuplicateTSBoilerplate(destFiles) {
|
||||
// Taken from typescript compiler => emitFiles
|
||||
var BOILERPLATE = [
|
||||
{ start: /^var __extends/, end: /^};$/ },
|
||||
{ start: /^var __assign/, end: /^};$/ },
|
||||
{ start: /^var __decorate/, end: /^};$/ },
|
||||
{ start: /^var __metadata/, end: /^};$/ },
|
||||
{ start: /^var __param/, end: /^};$/ },
|
||||
{ start: /^var __awaiter/, end: /^};$/ },
|
||||
];
|
||||
destFiles.forEach(function (destFile) {
|
||||
var SEEN_BOILERPLATE = [];
|
||||
destFile.sources.forEach(function (source) {
|
||||
var lines = source.contents.split(/\r\n|\n|\r/);
|
||||
var newLines = [];
|
||||
var IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i];
|
||||
if (IS_REMOVING_BOILERPLATE) {
|
||||
newLines.push('');
|
||||
if (END_BOILERPLATE.test(line)) {
|
||||
IS_REMOVING_BOILERPLATE = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var j = 0; j < BOILERPLATE.length; j++) {
|
||||
var boilerplate = BOILERPLATE[j];
|
||||
if (boilerplate.start.test(line)) {
|
||||
if (SEEN_BOILERPLATE[j]) {
|
||||
IS_REMOVING_BOILERPLATE = true;
|
||||
END_BOILERPLATE = boilerplate.end;
|
||||
}
|
||||
else {
|
||||
SEEN_BOILERPLATE[j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IS_REMOVING_BOILERPLATE) {
|
||||
newLines.push('');
|
||||
}
|
||||
else {
|
||||
newLines.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
source.contents = newLines.join('\n');
|
||||
});
|
||||
});
|
||||
return destFiles;
|
||||
}
|
||||
function emitEntryPoint(modulesMap, deps, entryPoint, includedModules) {
|
||||
var mainResult = {
|
||||
sources: [],
|
||||
dest: entryPoint + '.js'
|
||||
}, results = [mainResult];
|
||||
var usedPlugins = {};
|
||||
var getLoaderPlugin = function (pluginName) {
|
||||
if (!usedPlugins[pluginName]) {
|
||||
usedPlugins[pluginName] = modulesMap[pluginName].exports;
|
||||
}
|
||||
return usedPlugins[pluginName];
|
||||
};
|
||||
includedModules.forEach(function (c) {
|
||||
var bangIndex = c.indexOf('!');
|
||||
if (bangIndex >= 0) {
|
||||
var pluginName = c.substr(0, bangIndex);
|
||||
var plugin = getLoaderPlugin(pluginName);
|
||||
mainResult.sources.push(emitPlugin(entryPoint, plugin, pluginName, c.substr(bangIndex + 1)));
|
||||
return;
|
||||
}
|
||||
var module = modulesMap[c];
|
||||
if (module.path === 'empty:') {
|
||||
return;
|
||||
}
|
||||
var contents = readFileAndRemoveBOM(module.path);
|
||||
if (module.shim) {
|
||||
mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents));
|
||||
}
|
||||
else {
|
||||
mainResult.sources.push(emitNamedModule(c, deps[c], module.defineLocation, module.path, contents));
|
||||
}
|
||||
});
|
||||
Object.keys(usedPlugins).forEach(function (pluginName) {
|
||||
var plugin = usedPlugins[pluginName];
|
||||
if (typeof plugin.writeFile === 'function') {
|
||||
var req = (function () {
|
||||
throw new Error('no-no!');
|
||||
});
|
||||
req.toUrl = function (something) { return something; };
|
||||
var write = function (filename, contents) {
|
||||
results.push({
|
||||
dest: filename,
|
||||
sources: [{
|
||||
path: null,
|
||||
contents: contents
|
||||
}]
|
||||
});
|
||||
};
|
||||
plugin.writeFile(pluginName, entryPoint, req, write, {});
|
||||
}
|
||||
});
|
||||
return {
|
||||
files: results,
|
||||
usedPlugins: usedPlugins
|
||||
};
|
||||
}
|
||||
function readFileAndRemoveBOM(path) {
|
||||
var BOM_CHAR_CODE = 65279;
|
||||
var contents = fs.readFileSync(path, 'utf8');
|
||||
// Remove BOM
|
||||
if (contents.charCodeAt(0) === BOM_CHAR_CODE) {
|
||||
contents = contents.substring(1);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
function emitPlugin(entryPoint, plugin, pluginName, moduleName) {
|
||||
var result = '';
|
||||
if (typeof plugin.write === 'function') {
|
||||
var write = (function (what) {
|
||||
result += what;
|
||||
});
|
||||
write.getEntryPoint = function () {
|
||||
return entryPoint;
|
||||
};
|
||||
write.asModule = function (moduleId, code) {
|
||||
code = code.replace(/^define\(/, 'define("' + moduleId + '",');
|
||||
result += code;
|
||||
};
|
||||
plugin.write(pluginName, moduleName, write);
|
||||
}
|
||||
return {
|
||||
path: null,
|
||||
contents: result
|
||||
};
|
||||
}
|
||||
function emitNamedModule(moduleId, myDeps, defineCallPosition, path, contents) {
|
||||
// `defineCallPosition` is the position in code: |define()
|
||||
var defineCallOffset = positionToOffset(contents, defineCallPosition.line, defineCallPosition.col);
|
||||
// `parensOffset` is the position in code: define|()
|
||||
var parensOffset = contents.indexOf('(', defineCallOffset);
|
||||
var insertStr = '"' + moduleId + '", ';
|
||||
return {
|
||||
path: path,
|
||||
contents: contents.substr(0, parensOffset + 1) + insertStr + contents.substr(parensOffset + 1)
|
||||
};
|
||||
}
|
||||
function emitShimmedModule(moduleId, myDeps, factory, path, contents) {
|
||||
var strDeps = (myDeps.length > 0 ? '"' + myDeps.join('", "') + '"' : '');
|
||||
var strDefine = 'define("' + moduleId + '", [' + strDeps + '], ' + factory + ');';
|
||||
return {
|
||||
path: path,
|
||||
contents: contents + '\n;\n' + strDefine
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert a position (line:col) to (offset) in string `str`
|
||||
*/
|
||||
function positionToOffset(str, desiredLine, desiredCol) {
|
||||
if (desiredLine === 1) {
|
||||
return desiredCol - 1;
|
||||
}
|
||||
var line = 1, lastNewLineOffset = -1;
|
||||
do {
|
||||
if (desiredLine === line) {
|
||||
return lastNewLineOffset + 1 + desiredCol - 1;
|
||||
}
|
||||
lastNewLineOffset = str.indexOf('\n', lastNewLineOffset + 1);
|
||||
line++;
|
||||
} while (lastNewLineOffset >= 0);
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Return a set of reachable nodes in `graph` starting from `rootNodes`
|
||||
*/
|
||||
function visit(rootNodes, graph) {
|
||||
var result = {}, queue = rootNodes;
|
||||
rootNodes.forEach(function (node) {
|
||||
result[node] = true;
|
||||
});
|
||||
while (queue.length > 0) {
|
||||
var el = queue.shift();
|
||||
var myEdges = graph[el] || [];
|
||||
myEdges.forEach(function (toNode) {
|
||||
if (!result[toNode]) {
|
||||
result[toNode] = true;
|
||||
queue.push(toNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Perform a topological sort on `graph`
|
||||
*/
|
||||
function topologicalSort(graph) {
|
||||
var allNodes = {}, outgoingEdgeCount = {}, inverseEdges = {};
|
||||
Object.keys(graph).forEach(function (fromNode) {
|
||||
allNodes[fromNode] = true;
|
||||
outgoingEdgeCount[fromNode] = graph[fromNode].length;
|
||||
graph[fromNode].forEach(function (toNode) {
|
||||
allNodes[toNode] = true;
|
||||
outgoingEdgeCount[toNode] = outgoingEdgeCount[toNode] || 0;
|
||||
inverseEdges[toNode] = inverseEdges[toNode] || [];
|
||||
inverseEdges[toNode].push(fromNode);
|
||||
});
|
||||
});
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting
|
||||
var S = [], L = [];
|
||||
Object.keys(allNodes).forEach(function (node) {
|
||||
if (outgoingEdgeCount[node] === 0) {
|
||||
delete outgoingEdgeCount[node];
|
||||
S.push(node);
|
||||
}
|
||||
});
|
||||
while (S.length > 0) {
|
||||
// Ensure the exact same order all the time with the same inputs
|
||||
S.sort();
|
||||
var n = S.shift();
|
||||
L.push(n);
|
||||
var myInverseEdges = inverseEdges[n] || [];
|
||||
myInverseEdges.forEach(function (m) {
|
||||
outgoingEdgeCount[m]--;
|
||||
if (outgoingEdgeCount[m] === 0) {
|
||||
delete outgoingEdgeCount[m];
|
||||
S.push(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Object.keys(outgoingEdgeCount).length > 0) {
|
||||
throw new Error('Cannot do topological sort on cyclic graph, remaining nodes: ' + Object.keys(outgoingEdgeCount));
|
||||
}
|
||||
return L;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,22 @@ export interface IConcatFile {
|
|||
sources: IFile[];
|
||||
}
|
||||
|
||||
export interface IBundleData {
|
||||
graph: IGraph;
|
||||
bundles: {[moduleId:string]:string[];};
|
||||
}
|
||||
|
||||
export interface IBundleResult {
|
||||
files: IConcatFile[];
|
||||
cssInlinedResources: string[];
|
||||
bundleData: IBundleData;
|
||||
}
|
||||
|
||||
interface IPartialBundleResult {
|
||||
files: IConcatFile[];
|
||||
bundleData: IBundleData;
|
||||
}
|
||||
|
||||
export interface ILoaderConfig {
|
||||
isBuild?: boolean;
|
||||
}
|
||||
|
@ -77,12 +93,23 @@ export interface ILoaderConfig {
|
|||
/**
|
||||
* Bundle `entryPoints` given config `config`.
|
||||
*/
|
||||
export function bundle(entryPoints:IEntryPoint[], config:ILoaderConfig, callback:(err:any, result:IConcatFile[]) => void): void {
|
||||
export function bundle(entryPoints:IEntryPoint[], config:ILoaderConfig, callback:(err:any, result:IBundleResult) => void): void {
|
||||
let entryPointsMap:IEntryPointMap = {};
|
||||
entryPoints.forEach((module:IEntryPoint) => {
|
||||
entryPointsMap[module.name] = module;
|
||||
});
|
||||
|
||||
let allMentionedModulesMap: {[modules:string]:boolean;} = {};
|
||||
entryPoints.forEach((module:IEntryPoint) => {
|
||||
allMentionedModulesMap[module.name] = true;
|
||||
(module.include||[]).forEach(function(includedModule) {
|
||||
allMentionedModulesMap[includedModule] = true;
|
||||
});
|
||||
(module.exclude||[]).forEach(function(excludedModule) {
|
||||
allMentionedModulesMap[excludedModule] = true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js'));
|
||||
var r: Function = <any> vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});');
|
||||
|
@ -93,13 +120,19 @@ export function bundle(entryPoints:IEntryPoint[], config:ILoaderConfig, callback
|
|||
config.isBuild = true;
|
||||
loader.config(config);
|
||||
|
||||
loader(Object.keys(entryPointsMap), () => {
|
||||
loader(Object.keys(allMentionedModulesMap), () => {
|
||||
let modules = <IBuildModuleInfo[]>loader.getBuildInfo();
|
||||
callback(null, emitEntryPoints(modules, entryPointsMap));
|
||||
let partialResult = emitEntryPoints(modules, entryPointsMap);
|
||||
let cssInlinedResources = loader('vs/css').getInlinedResources();
|
||||
callback(null, {
|
||||
files: partialResult.files,
|
||||
cssInlinedResources: cssInlinedResources,
|
||||
bundleData: partialResult.bundleData
|
||||
});
|
||||
}, (err) => callback(err, null));
|
||||
}
|
||||
|
||||
function emitEntryPoints(modules:IBuildModuleInfo[], entryPoints:IEntryPointMap): IConcatFile[] {
|
||||
function emitEntryPoints(modules:IBuildModuleInfo[], entryPoints:IEntryPointMap): IPartialBundleResult {
|
||||
let modulesMap: IBuildModuleInfoMap = {};
|
||||
modules.forEach((m:IBuildModuleInfo) => {
|
||||
modulesMap[m.id] = m;
|
||||
|
@ -114,6 +147,10 @@ function emitEntryPoints(modules:IBuildModuleInfo[], entryPoints:IEntryPointMap)
|
|||
|
||||
let result: IConcatFile[] = [];
|
||||
let usedPlugins: IPluginMap = {};
|
||||
let bundleData:IBundleData = {
|
||||
graph: modulesGraph,
|
||||
bundles: {}
|
||||
};
|
||||
|
||||
Object.keys(entryPoints).forEach((moduleToBundle:string) => {
|
||||
let info = entryPoints[moduleToBundle];
|
||||
|
@ -132,6 +169,8 @@ function emitEntryPoints(modules:IBuildModuleInfo[], entryPoints:IEntryPointMap)
|
|||
return allDependencies[module];
|
||||
});
|
||||
|
||||
bundleData.bundles[moduleToBundle] = includedModules;
|
||||
|
||||
let res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules);
|
||||
|
||||
result = result.concat(res.files);
|
||||
|
@ -156,7 +195,155 @@ function emitEntryPoints(modules:IBuildModuleInfo[], entryPoints:IEntryPointMap)
|
|||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return {
|
||||
files: extractStrings(removeDuplicateTSBoilerplate(result)),
|
||||
bundleData: bundleData
|
||||
};
|
||||
}
|
||||
|
||||
function extractStrings(destFiles:IConcatFile[]):IConcatFile[] {
|
||||
let parseDefineCall = (moduleMatch:string, depsMatch:string) => {
|
||||
let module = moduleMatch.replace(/^"|"$/g, '');
|
||||
let deps = depsMatch.split(',');
|
||||
deps = deps.map((dep) => {
|
||||
dep = dep.trim();
|
||||
dep = dep.replace(/^"|"$/g, '');
|
||||
dep = dep.replace(/^'|'$/g, '');
|
||||
let prefix:string = null;
|
||||
let _path:string = null;
|
||||
let pieces = dep.split('!');
|
||||
if (pieces.length > 1) {
|
||||
prefix = pieces[0] + '!';
|
||||
_path = pieces[1];
|
||||
} else {
|
||||
prefix = '';
|
||||
_path = pieces[0];
|
||||
}
|
||||
|
||||
if (/^\.\//.test(_path) || /^\.\.\//.test(_path)) {
|
||||
let res = path.join(path.dirname(module), _path).replace(/\\/g, '/');
|
||||
return prefix + res;
|
||||
}
|
||||
return prefix + _path;
|
||||
});
|
||||
return {
|
||||
module: module,
|
||||
deps: deps
|
||||
};
|
||||
};
|
||||
|
||||
destFiles.forEach((destFile, index) => {
|
||||
if (!/\.js$/.test(destFile.dest)) {
|
||||
return;
|
||||
}
|
||||
if (/\.nls\.js$/.test(destFile.dest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do one pass to record the usage counts for each module id
|
||||
let useCounts: {[moduleId:string]:number;} = {};
|
||||
destFile.sources.forEach((source) => {
|
||||
let matches = source.contents.match(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
let defineCall = parseDefineCall(matches[1], matches[2]);
|
||||
useCounts[defineCall.module] = (useCounts[defineCall.module] || 0) + 1;
|
||||
defineCall.deps.forEach((dep) => {
|
||||
useCounts[dep] = (useCounts[dep] || 0) + 1;
|
||||
});
|
||||
});
|
||||
|
||||
let sortedByUseModules = Object.keys(useCounts);
|
||||
sortedByUseModules.sort((a, b) => {
|
||||
return useCounts[b] - useCounts[a];
|
||||
});
|
||||
|
||||
let replacementMap: {[moduleId:string]:number;} = {};
|
||||
sortedByUseModules.forEach((module, index) => {
|
||||
replacementMap[module] = index;
|
||||
});
|
||||
|
||||
destFile.sources.forEach((source) => {
|
||||
source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, (_, moduleMatch, depsMatch) => {
|
||||
let defineCall = parseDefineCall(moduleMatch, depsMatch);
|
||||
return `define(__m[${replacementMap[defineCall.module]}], __M([${defineCall.deps.map(dep => replacementMap[dep]).join(',')}])`;
|
||||
});
|
||||
});
|
||||
|
||||
destFile.sources.unshift({
|
||||
path: null,
|
||||
contents: [
|
||||
'(function() {',
|
||||
`var __m = ${JSON.stringify(sortedByUseModules)};`,
|
||||
`var __M = function(deps) {`,
|
||||
` var result = [];`,
|
||||
` for (var i = 0, len = deps.length; i < len; i++) {`,
|
||||
` result[i] = __m[deps[i]];`,
|
||||
` }`,
|
||||
` return result;`,
|
||||
`};`
|
||||
].join('\n')
|
||||
});
|
||||
|
||||
destFile.sources.push({
|
||||
path: null,
|
||||
contents: '}).call(this);'
|
||||
});
|
||||
});
|
||||
return destFiles;
|
||||
}
|
||||
|
||||
function removeDuplicateTSBoilerplate(destFiles:IConcatFile[]):IConcatFile[] {
|
||||
// Taken from typescript compiler => emitFiles
|
||||
let BOILERPLATE = [
|
||||
{ start: /^var __extends/, end: /^};$/ },
|
||||
{ start: /^var __assign/, end: /^};$/ },
|
||||
{ start: /^var __decorate/, end: /^};$/ },
|
||||
{ start: /^var __metadata/, end: /^};$/ },
|
||||
{ start: /^var __param/, end: /^};$/ },
|
||||
{ start: /^var __awaiter/, end: /^};$/ },
|
||||
];
|
||||
|
||||
destFiles.forEach((destFile) => {
|
||||
let SEEN_BOILERPLATE = [];
|
||||
destFile.sources.forEach((source) => {
|
||||
let lines = source.contents.split(/\r\n|\n|\r/);
|
||||
let newLines: string[] = [];
|
||||
let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (IS_REMOVING_BOILERPLATE) {
|
||||
newLines.push('');
|
||||
if (END_BOILERPLATE.test(line)) {
|
||||
IS_REMOVING_BOILERPLATE = false;
|
||||
}
|
||||
} else {
|
||||
for (let j = 0; j < BOILERPLATE.length; j++) {
|
||||
let boilerplate = BOILERPLATE[j];
|
||||
if (boilerplate.start.test(line)) {
|
||||
if (SEEN_BOILERPLATE[j]) {
|
||||
IS_REMOVING_BOILERPLATE = true;
|
||||
END_BOILERPLATE = boilerplate.end;
|
||||
} else {
|
||||
SEEN_BOILERPLATE[j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IS_REMOVING_BOILERPLATE) {
|
||||
newLines.push('');
|
||||
} else {
|
||||
newLines.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
source.contents = newLines.join('\n');
|
||||
});
|
||||
});
|
||||
|
||||
return destFiles;
|
||||
}
|
||||
|
||||
interface IPluginMap {
|
||||
|
|
|
@ -91,12 +91,6 @@ function sortLanguages(directoryNames) {
|
|||
return a.iso639_2 < b.iso639_2 ? -1 : (a.iso639_2 > b.iso639_2 ? 1 : 0);
|
||||
});
|
||||
}
|
||||
var headerComment = [
|
||||
'/*---------------------------------------------------------------------------------------------',
|
||||
' * Copyright (c) Microsoft Corporation. All rights reserved.',
|
||||
' * Licensed under the MIT License. See License.txt in the project root for license information.',
|
||||
' *---------------------------------------------------------------------------------------------*/'
|
||||
].join('\n');
|
||||
function stripComments(content) {
|
||||
/**
|
||||
* First capturing group matches double quoted string
|
||||
|
@ -164,7 +158,7 @@ function escapeCharacters(value) {
|
|||
}
|
||||
return result.join('');
|
||||
}
|
||||
function processCoreBundleFormat(json, emitter) {
|
||||
function processCoreBundleFormat(fileHeader, json, emitter) {
|
||||
var keysSection = json.keys;
|
||||
var messageSection = json.messages;
|
||||
var bundleSection = json.bundles;
|
||||
|
@ -236,7 +230,7 @@ function processCoreBundleFormat(json, emitter) {
|
|||
Object.keys(bundleSection).forEach(function (bundle) {
|
||||
var modules = bundleSection[bundle];
|
||||
var contents = [
|
||||
headerComment,
|
||||
fileHeader,
|
||||
("define(\"" + bundle + ".nls." + language.iso639_2 + "\", {")
|
||||
];
|
||||
modules.forEach(function (module, index) {
|
||||
|
@ -273,7 +267,7 @@ function processCoreBundleFormat(json, emitter) {
|
|||
}
|
||||
});
|
||||
}
|
||||
function processNlsFiles() {
|
||||
function processNlsFiles(opts) {
|
||||
return event_stream_1.through(function (file) {
|
||||
var fileName = path.basename(file.path);
|
||||
if (fileName === 'nls.metadata.json') {
|
||||
|
@ -285,7 +279,7 @@ function processNlsFiles() {
|
|||
this.emit('error', "Failed to read component file: " + file.relative);
|
||||
}
|
||||
if (BundledFormat.is(json)) {
|
||||
processCoreBundleFormat(json, this);
|
||||
processCoreBundleFormat(opts.fileHeader, json, this);
|
||||
}
|
||||
}
|
||||
this.emit('data', file);
|
||||
|
|
|
@ -115,15 +115,6 @@ function sortLanguages(directoryNames: string[]): IDirectoryInfo[] {
|
|||
});
|
||||
}
|
||||
|
||||
const headerComment: string =
|
||||
[
|
||||
'/*---------------------------------------------------------------------------------------------',
|
||||
' * Copyright (c) Microsoft Corporation. All rights reserved.',
|
||||
' * Licensed under the MIT License. See License.txt in the project root for license information.',
|
||||
' *---------------------------------------------------------------------------------------------*/'
|
||||
].join('\n');
|
||||
|
||||
|
||||
function stripComments(content: string): string {
|
||||
/**
|
||||
* First capturing group matches double quoted string
|
||||
|
@ -189,7 +180,7 @@ function escapeCharacters(value:string):string {
|
|||
return result.join('');
|
||||
}
|
||||
|
||||
function processCoreBundleFormat(json: BundledFormat, emitter: any) {
|
||||
function processCoreBundleFormat(fileHeader:string, json: BundledFormat, emitter: any) {
|
||||
let keysSection = json.keys;
|
||||
let messageSection = json.messages;
|
||||
let bundleSection = json.bundles;
|
||||
|
@ -262,7 +253,7 @@ function processCoreBundleFormat(json: BundledFormat, emitter: any) {
|
|||
Object.keys(bundleSection).forEach((bundle) => {
|
||||
let modules = bundleSection[bundle];
|
||||
let contents: string[] = [
|
||||
headerComment,
|
||||
fileHeader,
|
||||
`define("${bundle}.nls.${language.iso639_2}", {`
|
||||
];
|
||||
modules.forEach((module, index) => {
|
||||
|
@ -299,7 +290,7 @@ function processCoreBundleFormat(json: BundledFormat, emitter: any) {
|
|||
});
|
||||
}
|
||||
|
||||
export function processNlsFiles(): ThroughStream {
|
||||
export function processNlsFiles(opts:{fileHeader:string;}): ThroughStream {
|
||||
return through(function(file: File) {
|
||||
let fileName = path.basename(file.path);
|
||||
if (fileName === 'nls.metadata.json') {
|
||||
|
@ -310,7 +301,7 @@ export function processNlsFiles(): ThroughStream {
|
|||
this.emit('error', `Failed to read component file: ${file.relative}`)
|
||||
}
|
||||
if (BundledFormat.is(json)) {
|
||||
processCoreBundleFormat(json, this);
|
||||
processCoreBundleFormat(opts.fileHeader, json, this);
|
||||
}
|
||||
}
|
||||
this.emit('data', file);
|
||||
|
|
|
@ -183,7 +183,7 @@ exports.skipDirectories = function () {
|
|||
});
|
||||
};
|
||||
|
||||
exports.cleanNodeModule = function (name, excludes, isNative) {
|
||||
exports.cleanNodeModule = function (name, excludes, includes) {
|
||||
var glob = function (path) { return '**/node_modules/' + name + (path ? '/' + path : ''); };
|
||||
var negate = function (str) { return '!' + str; };
|
||||
|
||||
|
@ -194,8 +194,9 @@ exports.cleanNodeModule = function (name, excludes, isNative) {
|
|||
var nodeModuleInput = input.pipe(allFilter);
|
||||
var output = nodeModuleInput.pipe(filter(globs));
|
||||
|
||||
if (isNative) {
|
||||
output = es.merge(output, nodeModuleInput.pipe(filter(glob('**/*.node'))));
|
||||
if (includes) {
|
||||
var includeGlobs = includes.map(glob);
|
||||
output = es.merge(output, nodeModuleInput.pipe(filter(includeGlobs)));
|
||||
}
|
||||
|
||||
output = output.pipe(allFilter.restore);
|
||||
|
|
21
build/monaco/LICENSE
Normal file
21
build/monaco/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
14
build/monaco/README-npm.md
Normal file
14
build/monaco/README-npm.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# monaco-editor-core
|
||||
|
||||
> This npm module is a building block for the [monaco-editor](https://www.npmjs.com/package/monaco-editor)
|
||||
npm module and unless you are doing something special (e.g. authoring a monaco editor language that can be shipped
|
||||
and consumed independently), it is best to consume the [monaco-editor](https://www.npmjs.com/package/monaco-editor) module
|
||||
that contains this module and adds languages supports.
|
||||
|
||||
The Monaco Editor is the code editor that powers [VS Code](https://github.com/Microsoft/vscode),
|
||||
a good page describing the code editor's features is [here](https://code.visualstudio.com/docs/editor/editingevolved).
|
||||
|
||||
This npm module contains the core editor functionality, as it comes from the [vscode repository](https://github.com/Microsoft/vscode).
|
||||
|
||||
## License
|
||||
[MIT](https://github.com/Microsoft/vscode/blob/master/LICENSE.txt)
|
20
build/monaco/README.md
Normal file
20
build/monaco/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Steps to publish a new version of monaco-editor-core
|
||||
|
||||
## Generate monaco.d.ts
|
||||
|
||||
* The `monaco.d.ts` is now automatically generated when running `gulp watch`
|
||||
|
||||
## Bump version
|
||||
|
||||
* increase version in `build/monaco/package.json`
|
||||
|
||||
## Generate npm contents for monaco-editor-core
|
||||
|
||||
* Be sure to have all changes committed **and pushed to the remote**
|
||||
* (the generated files contain the HEAD sha and that should be available on the remote)
|
||||
* run gulp editor-distro
|
||||
|
||||
## Publish
|
||||
|
||||
* `cd out-monaco-editor-core`
|
||||
* `npm publish`
|
72
build/monaco/ThirdPartyNotices.txt
Normal file
72
build/monaco/ThirdPartyNotices.txt
Normal file
|
@ -0,0 +1,72 @@
|
|||
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
|
||||
Do Not Translate or Localize
|
||||
|
||||
This project incorporates components from the projects listed below. The original copyright notices and the licenses
|
||||
under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted
|
||||
herein, whether by implication, estoppel or otherwise.
|
||||
|
||||
|
||||
|
||||
|
||||
%% HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
|
||||
=========================================
|
||||
Copyright © 2015 W3C® (MIT, ERCIM, Keio, Beihang). This software or document includes material copied
|
||||
from or derived from HTML 5.1 W3C Working Draft (http://www.w3.org/TR/2015/WD-html51-20151008/.)
|
||||
|
||||
THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT
|
||||
NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE CONTENTS OF
|
||||
THE DOCUMENT ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY
|
||||
PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
|
||||
|
||||
COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE
|
||||
DOCUMENT OR THE PERFORMANCE OR IMPLEMENTATION OF THE CONTENTS THEREOF.
|
||||
|
||||
The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to this document or its contents
|
||||
without specific, written prior permission. Title to copyright in this document will at all times remain with copyright holders.
|
||||
=========================================
|
||||
END OF HTML 5.1 W3C Working Draft NOTICES AND INFORMATION
|
||||
|
||||
|
||||
|
||||
|
||||
%% js-beautify version 1.5.10 (https://github.com/beautify-web/js-beautify)
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2007-2013 Einar Lielmanis and contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
=========================================
|
||||
END OF js-beautify NOTICES AND INFORMATION
|
||||
|
||||
|
||||
|
||||
|
||||
%% string_scorer version 0.1.20 (https://github.com/joshaven/string_score)
|
||||
=========================================
|
||||
This software is released under the MIT license:
|
||||
|
||||
Copyright (c) Joshaven Potter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
=========================================
|
||||
END OF string_scorer NOTICES AND INFORMATION
|
342
build/monaco/api.js
Normal file
342
build/monaco/api.js
Normal file
|
@ -0,0 +1,342 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
"use strict";
|
||||
var fs = require('fs');
|
||||
var ts = require('typescript');
|
||||
var path = require('path');
|
||||
var util = require('gulp-util');
|
||||
function log(message) {
|
||||
var rest = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
rest[_i - 1] = arguments[_i];
|
||||
}
|
||||
util.log.apply(util, [util.colors.cyan('[monaco.d.ts]'), message].concat(rest));
|
||||
}
|
||||
var SRC = path.join(__dirname, '../../src');
|
||||
var OUT_ROOT = path.join(__dirname, '../../');
|
||||
var RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe');
|
||||
var DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts');
|
||||
var CURRENT_PROCESSING_RULE = '';
|
||||
function logErr(message) {
|
||||
var rest = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
rest[_i - 1] = arguments[_i];
|
||||
}
|
||||
util.log(util.colors.red('[monaco.d.ts]'), 'WHILE HANDLING RULE: ', CURRENT_PROCESSING_RULE);
|
||||
util.log.apply(util, [util.colors.red('[monaco.d.ts]'), message].concat(rest));
|
||||
}
|
||||
function moduleIdToPath(out, moduleId) {
|
||||
if (/\.d\.ts/.test(moduleId)) {
|
||||
return path.join(SRC, moduleId);
|
||||
}
|
||||
return path.join(OUT_ROOT, out, moduleId) + '.d.ts';
|
||||
}
|
||||
var SOURCE_FILE_MAP = {};
|
||||
function getSourceFile(out, moduleId) {
|
||||
if (!SOURCE_FILE_MAP[moduleId]) {
|
||||
var filePath = moduleIdToPath(out, moduleId);
|
||||
var fileContents = void 0;
|
||||
try {
|
||||
fileContents = fs.readFileSync(filePath).toString();
|
||||
}
|
||||
catch (err) {
|
||||
logErr('CANNOT FIND FILE ' + filePath);
|
||||
return null;
|
||||
}
|
||||
var sourceFile = ts.createSourceFile(filePath, fileContents, ts.ScriptTarget.ES5);
|
||||
SOURCE_FILE_MAP[moduleId] = sourceFile;
|
||||
}
|
||||
return SOURCE_FILE_MAP[moduleId];
|
||||
}
|
||||
function isDeclaration(a) {
|
||||
return (a.kind === ts.SyntaxKind.InterfaceDeclaration
|
||||
|| a.kind === ts.SyntaxKind.EnumDeclaration
|
||||
|| a.kind === ts.SyntaxKind.ClassDeclaration
|
||||
|| a.kind === ts.SyntaxKind.TypeAliasDeclaration
|
||||
|| a.kind === ts.SyntaxKind.FunctionDeclaration
|
||||
|| a.kind === ts.SyntaxKind.ModuleDeclaration);
|
||||
}
|
||||
function visitTopLevelDeclarations(sourceFile, visitor) {
|
||||
var stop = false;
|
||||
var visit = function (node) {
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.InterfaceDeclaration:
|
||||
case ts.SyntaxKind.EnumDeclaration:
|
||||
case ts.SyntaxKind.ClassDeclaration:
|
||||
case ts.SyntaxKind.VariableStatement:
|
||||
case ts.SyntaxKind.TypeAliasDeclaration:
|
||||
case ts.SyntaxKind.FunctionDeclaration:
|
||||
case ts.SyntaxKind.ModuleDeclaration:
|
||||
stop = visitor(node);
|
||||
}
|
||||
// if (node.kind !== ts.SyntaxKind.SourceFile) {
|
||||
// if (getNodeText(sourceFile, node).indexOf('SymbolKind') >= 0) {
|
||||
// console.log('FOUND TEXT IN NODE: ' + ts.SyntaxKind[node.kind]);
|
||||
// console.log(getNodeText(sourceFile, node));
|
||||
// }
|
||||
// }
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
}
|
||||
function getAllTopLevelDeclarations(sourceFile) {
|
||||
var all = [];
|
||||
visitTopLevelDeclarations(sourceFile, function (node) {
|
||||
if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) {
|
||||
var interfaceDeclaration = node;
|
||||
var triviaStart = interfaceDeclaration.pos;
|
||||
var triviaEnd = interfaceDeclaration.name.pos;
|
||||
var triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd });
|
||||
// // let nodeText = getNodeText(sourceFile, node);
|
||||
// if (getNodeText(sourceFile, node).indexOf('SymbolKind') >= 0) {
|
||||
// console.log('TRIVIA: ', triviaText);
|
||||
// }
|
||||
if (triviaText.indexOf('@internal') === -1) {
|
||||
all.push(node);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var nodeText = getNodeText(sourceFile, node);
|
||||
if (nodeText.indexOf('@internal') === -1) {
|
||||
all.push(node);
|
||||
}
|
||||
}
|
||||
return false /*continue*/;
|
||||
});
|
||||
return all;
|
||||
}
|
||||
function getTopLevelDeclaration(sourceFile, typeName) {
|
||||
var result = null;
|
||||
visitTopLevelDeclarations(sourceFile, function (node) {
|
||||
if (isDeclaration(node)) {
|
||||
if (node.name.text === typeName) {
|
||||
result = node;
|
||||
return true /*stop*/;
|
||||
}
|
||||
return false /*continue*/;
|
||||
}
|
||||
// node is ts.VariableStatement
|
||||
if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) {
|
||||
result = node;
|
||||
return true /*stop*/;
|
||||
}
|
||||
return false /*continue*/;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function getNodeText(sourceFile, node) {
|
||||
return sourceFile.getFullText().substring(node.pos, node.end);
|
||||
}
|
||||
function getMassagedTopLevelDeclarationText(sourceFile, declaration) {
|
||||
var result = getNodeText(sourceFile, declaration);
|
||||
// if (result.indexOf('MonacoWorker') >= 0) {
|
||||
// console.log('here!');
|
||||
// // console.log(ts.SyntaxKind[declaration.kind]);
|
||||
// }
|
||||
if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) {
|
||||
var interfaceDeclaration = declaration;
|
||||
var members = interfaceDeclaration.members;
|
||||
members.forEach(function (member) {
|
||||
try {
|
||||
var memberText = getNodeText(sourceFile, member);
|
||||
if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) {
|
||||
// console.log('BEFORE: ', result);
|
||||
result = result.replace(memberText, '');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
}
|
||||
});
|
||||
}
|
||||
result = result.replace(/export default/g, 'export');
|
||||
result = result.replace(/export declare/g, 'export');
|
||||
return result;
|
||||
}
|
||||
function format(text) {
|
||||
var options = getDefaultOptions();
|
||||
// Parse the source text
|
||||
var sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true);
|
||||
// Get the formatting edits on the input sources
|
||||
var edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(options), options);
|
||||
// Apply the edits on the input code
|
||||
return applyEdits(text, edits);
|
||||
function getRuleProvider(options) {
|
||||
// Share this between multiple formatters using the same options.
|
||||
// This represents the bulk of the space the formatter uses.
|
||||
var ruleProvider = new ts.formatting.RulesProvider();
|
||||
ruleProvider.ensureUpToDate(options);
|
||||
return ruleProvider;
|
||||
}
|
||||
function applyEdits(text, edits) {
|
||||
// Apply edits in reverse on the existing text
|
||||
var result = text;
|
||||
for (var i = edits.length - 1; i >= 0; i--) {
|
||||
var change = edits[i];
|
||||
var head = result.slice(0, change.span.start);
|
||||
var tail = result.slice(change.span.start + change.span.length);
|
||||
result = head + change.newText + tail;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function getDefaultOptions() {
|
||||
return {
|
||||
IndentSize: 4,
|
||||
TabSize: 4,
|
||||
NewLineCharacter: '\r\n',
|
||||
ConvertTabsToSpaces: true,
|
||||
IndentStyle: ts.IndentStyle.Block,
|
||||
InsertSpaceAfterCommaDelimiter: true,
|
||||
InsertSpaceAfterSemicolonInForStatements: true,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true,
|
||||
PlaceOpenBraceOnNewLineForFunctions: false,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: false
|
||||
};
|
||||
}
|
||||
}
|
||||
function createReplacer(data) {
|
||||
data = data || '';
|
||||
var rawDirectives = data.split(';');
|
||||
var directives = [];
|
||||
rawDirectives.forEach(function (rawDirective) {
|
||||
if (rawDirective.length === 0) {
|
||||
return;
|
||||
}
|
||||
var pieces = rawDirective.split('=>');
|
||||
var findStr = pieces[0];
|
||||
var replaceStr = pieces[1];
|
||||
findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&');
|
||||
findStr = '\\b' + findStr + '\\b';
|
||||
directives.push([new RegExp(findStr, 'g'), replaceStr]);
|
||||
});
|
||||
return function (str) {
|
||||
for (var i = 0; i < directives.length; i++) {
|
||||
str = str.replace(directives[i][0], directives[i][1]);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
}
|
||||
function generateDeclarationFile(out, recipe) {
|
||||
var lines = recipe.split(/\r\n|\n|\r/);
|
||||
var result = [];
|
||||
lines.forEach(function (line) {
|
||||
var m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m1) {
|
||||
CURRENT_PROCESSING_RULE = line;
|
||||
var moduleId = m1[1];
|
||||
var sourceFile_1 = getSourceFile(out, moduleId);
|
||||
if (!sourceFile_1) {
|
||||
return;
|
||||
}
|
||||
var replacer_1 = createReplacer(m1[2]);
|
||||
var typeNames = m1[3].split(/,/);
|
||||
typeNames.forEach(function (typeName) {
|
||||
typeName = typeName.trim();
|
||||
if (typeName.length === 0) {
|
||||
return;
|
||||
}
|
||||
var declaration = getTopLevelDeclaration(sourceFile_1, typeName);
|
||||
if (!declaration) {
|
||||
logErr('Cannot find type ' + typeName);
|
||||
return;
|
||||
}
|
||||
result.push(replacer_1(getMassagedTopLevelDeclarationText(sourceFile_1, declaration)));
|
||||
});
|
||||
return;
|
||||
}
|
||||
var m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m2) {
|
||||
CURRENT_PROCESSING_RULE = line;
|
||||
var moduleId = m2[1];
|
||||
var sourceFile_2 = getSourceFile(out, moduleId);
|
||||
if (!sourceFile_2) {
|
||||
return;
|
||||
}
|
||||
var replacer_2 = createReplacer(m2[2]);
|
||||
var typeNames = m2[3].split(/,/);
|
||||
var typesToExcludeMap_1 = {};
|
||||
var typesToExcludeArr_1 = [];
|
||||
typeNames.forEach(function (typeName) {
|
||||
typeName = typeName.trim();
|
||||
if (typeName.length === 0) {
|
||||
return;
|
||||
}
|
||||
typesToExcludeMap_1[typeName] = true;
|
||||
typesToExcludeArr_1.push(typeName);
|
||||
});
|
||||
getAllTopLevelDeclarations(sourceFile_2).forEach(function (declaration) {
|
||||
if (isDeclaration(declaration)) {
|
||||
if (typesToExcludeMap_1[declaration.name.text]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// node is ts.VariableStatement
|
||||
var nodeText = getNodeText(sourceFile_2, declaration);
|
||||
for (var i = 0; i < typesToExcludeArr_1.length; i++) {
|
||||
if (nodeText.indexOf(typesToExcludeArr_1[i]) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(replacer_2(getMassagedTopLevelDeclarationText(sourceFile_2, declaration)));
|
||||
});
|
||||
return;
|
||||
}
|
||||
result.push(line);
|
||||
});
|
||||
var resultTxt = result.join('\n');
|
||||
resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri');
|
||||
resultTxt = resultTxt.replace(/\bEvent</g, 'IEvent<');
|
||||
resultTxt = resultTxt.replace(/\bTPromise</g, 'Promise<');
|
||||
resultTxt = format(resultTxt);
|
||||
resultTxt = resultTxt.replace(/\r\n/g, '\n');
|
||||
return resultTxt;
|
||||
}
|
||||
function getFilesToWatch(out) {
|
||||
var recipe = fs.readFileSync(RECIPE_PATH).toString();
|
||||
var lines = recipe.split(/\r\n|\n|\r/);
|
||||
var result = [];
|
||||
lines.forEach(function (line) {
|
||||
var m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m1) {
|
||||
var moduleId = m1[1];
|
||||
result.push(moduleIdToPath(out, moduleId));
|
||||
return;
|
||||
}
|
||||
var m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m2) {
|
||||
var moduleId = m2[1];
|
||||
result.push(moduleIdToPath(out, moduleId));
|
||||
return;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
exports.getFilesToWatch = getFilesToWatch;
|
||||
function run(out) {
|
||||
log('Starting monaco.d.ts generation');
|
||||
SOURCE_FILE_MAP = {};
|
||||
var recipe = fs.readFileSync(RECIPE_PATH).toString();
|
||||
var result = generateDeclarationFile(out, recipe);
|
||||
var currentContent = fs.readFileSync(DECLARATION_PATH).toString();
|
||||
log('Finished monaco.d.ts generation');
|
||||
return {
|
||||
content: result,
|
||||
filePath: DECLARATION_PATH,
|
||||
isTheSame: currentContent === result
|
||||
};
|
||||
}
|
||||
exports.run = run;
|
401
build/monaco/api.ts
Normal file
401
build/monaco/api.ts
Normal file
|
@ -0,0 +1,401 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import fs = require('fs');
|
||||
import ts = require('typescript');
|
||||
import path = require('path');
|
||||
|
||||
var util = require('gulp-util');
|
||||
function log(message: any, ...rest: any[]): void {
|
||||
util.log(util.colors.cyan('[monaco.d.ts]'), message, ...rest);
|
||||
}
|
||||
|
||||
const SRC = path.join(__dirname, '../../src');
|
||||
const OUT_ROOT = path.join(__dirname, '../../');
|
||||
const RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe');
|
||||
const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts');
|
||||
|
||||
var CURRENT_PROCESSING_RULE = '';
|
||||
function logErr(message: any, ...rest: any[]): void {
|
||||
util.log(util.colors.red('[monaco.d.ts]'), 'WHILE HANDLING RULE: ', CURRENT_PROCESSING_RULE);
|
||||
util.log(util.colors.red('[monaco.d.ts]'), message, ...rest);
|
||||
}
|
||||
|
||||
function moduleIdToPath(out:string, moduleId:string): string {
|
||||
if (/\.d\.ts/.test(moduleId)) {
|
||||
return path.join(SRC, moduleId);
|
||||
}
|
||||
return path.join(OUT_ROOT, out, moduleId) + '.d.ts';
|
||||
}
|
||||
|
||||
let SOURCE_FILE_MAP: {[moduleId:string]:ts.SourceFile;} = {};
|
||||
function getSourceFile(out:string, moduleId:string): ts.SourceFile {
|
||||
if (!SOURCE_FILE_MAP[moduleId]) {
|
||||
let filePath = moduleIdToPath(out, moduleId);
|
||||
|
||||
let fileContents: string;
|
||||
try {
|
||||
fileContents = fs.readFileSync(filePath).toString();
|
||||
} catch (err) {
|
||||
logErr('CANNOT FIND FILE ' + filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
let sourceFile = ts.createSourceFile(filePath, fileContents, ts.ScriptTarget.ES5);
|
||||
|
||||
SOURCE_FILE_MAP[moduleId] = sourceFile;
|
||||
}
|
||||
return SOURCE_FILE_MAP[moduleId];
|
||||
}
|
||||
|
||||
|
||||
type TSTopLevelDeclaration = ts.InterfaceDeclaration | ts.EnumDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration | ts.ModuleDeclaration;
|
||||
type TSTopLevelDeclare = TSTopLevelDeclaration | ts.VariableStatement;
|
||||
|
||||
function isDeclaration(a:TSTopLevelDeclare): a is TSTopLevelDeclaration {
|
||||
return (
|
||||
a.kind === ts.SyntaxKind.InterfaceDeclaration
|
||||
|| a.kind === ts.SyntaxKind.EnumDeclaration
|
||||
|| a.kind === ts.SyntaxKind.ClassDeclaration
|
||||
|| a.kind === ts.SyntaxKind.TypeAliasDeclaration
|
||||
|| a.kind === ts.SyntaxKind.FunctionDeclaration
|
||||
|| a.kind === ts.SyntaxKind.ModuleDeclaration
|
||||
);
|
||||
}
|
||||
|
||||
function visitTopLevelDeclarations(sourceFile:ts.SourceFile, visitor:(node:TSTopLevelDeclare)=>boolean): void {
|
||||
let stop = false;
|
||||
|
||||
let visit = (node: ts.Node): void => {
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.InterfaceDeclaration:
|
||||
case ts.SyntaxKind.EnumDeclaration:
|
||||
case ts.SyntaxKind.ClassDeclaration:
|
||||
case ts.SyntaxKind.VariableStatement:
|
||||
case ts.SyntaxKind.TypeAliasDeclaration:
|
||||
case ts.SyntaxKind.FunctionDeclaration:
|
||||
case ts.SyntaxKind.ModuleDeclaration:
|
||||
stop = visitor(<TSTopLevelDeclare>node);
|
||||
}
|
||||
|
||||
// if (node.kind !== ts.SyntaxKind.SourceFile) {
|
||||
// if (getNodeText(sourceFile, node).indexOf('SymbolKind') >= 0) {
|
||||
// console.log('FOUND TEXT IN NODE: ' + ts.SyntaxKind[node.kind]);
|
||||
// console.log(getNodeText(sourceFile, node));
|
||||
// }
|
||||
// }
|
||||
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
}
|
||||
|
||||
|
||||
function getAllTopLevelDeclarations(sourceFile:ts.SourceFile): TSTopLevelDeclare[] {
|
||||
let all:TSTopLevelDeclare[] = [];
|
||||
visitTopLevelDeclarations(sourceFile, (node) => {
|
||||
if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) {
|
||||
let interfaceDeclaration = <ts.InterfaceDeclaration>node;
|
||||
let triviaStart = interfaceDeclaration.pos;
|
||||
let triviaEnd = interfaceDeclaration.name.pos;
|
||||
let triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd });
|
||||
|
||||
// // let nodeText = getNodeText(sourceFile, node);
|
||||
// if (getNodeText(sourceFile, node).indexOf('SymbolKind') >= 0) {
|
||||
// console.log('TRIVIA: ', triviaText);
|
||||
// }
|
||||
if (triviaText.indexOf('@internal') === -1) {
|
||||
all.push(node);
|
||||
}
|
||||
} else {
|
||||
let nodeText = getNodeText(sourceFile, node);
|
||||
if (nodeText.indexOf('@internal') === -1) {
|
||||
all.push(node);
|
||||
}
|
||||
}
|
||||
return false /*continue*/;
|
||||
});
|
||||
return all;
|
||||
}
|
||||
|
||||
|
||||
function getTopLevelDeclaration(sourceFile:ts.SourceFile, typeName:string): TSTopLevelDeclare {
|
||||
let result:TSTopLevelDeclare = null;
|
||||
visitTopLevelDeclarations(sourceFile, (node) => {
|
||||
if (isDeclaration(node)) {
|
||||
if (node.name.text === typeName) {
|
||||
result = node;
|
||||
return true /*stop*/;
|
||||
}
|
||||
return false /*continue*/;
|
||||
}
|
||||
// node is ts.VariableStatement
|
||||
if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) {
|
||||
result = node;
|
||||
return true /*stop*/;
|
||||
}
|
||||
return false /*continue*/;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function getNodeText(sourceFile:ts.SourceFile, node:{pos:number; end:number;}): string {
|
||||
return sourceFile.getFullText().substring(node.pos, node.end);
|
||||
}
|
||||
|
||||
|
||||
function getMassagedTopLevelDeclarationText(sourceFile:ts.SourceFile, declaration: TSTopLevelDeclare): string {
|
||||
let result = getNodeText(sourceFile, declaration);
|
||||
// if (result.indexOf('MonacoWorker') >= 0) {
|
||||
// console.log('here!');
|
||||
// // console.log(ts.SyntaxKind[declaration.kind]);
|
||||
// }
|
||||
if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) {
|
||||
let interfaceDeclaration = <ts.InterfaceDeclaration | ts.ClassDeclaration>declaration;
|
||||
|
||||
let members:ts.NodeArray<ts.Node> = interfaceDeclaration.members;
|
||||
members.forEach((member) => {
|
||||
try {
|
||||
let memberText = getNodeText(sourceFile, member);
|
||||
if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) {
|
||||
// console.log('BEFORE: ', result);
|
||||
result = result.replace(memberText, '');
|
||||
// console.log('AFTER: ', result);
|
||||
}
|
||||
} catch (err) {
|
||||
// life..
|
||||
}
|
||||
});
|
||||
}
|
||||
result = result.replace(/export default/g, 'export');
|
||||
result = result.replace(/export declare/g, 'export');
|
||||
return result;
|
||||
}
|
||||
|
||||
function format(text:string): string {
|
||||
let options = getDefaultOptions();
|
||||
|
||||
// Parse the source text
|
||||
let sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true);
|
||||
|
||||
// Get the formatting edits on the input sources
|
||||
let edits = (<any>ts).formatting.formatDocument(sourceFile, getRuleProvider(options), options);
|
||||
|
||||
// Apply the edits on the input code
|
||||
return applyEdits(text, edits);
|
||||
|
||||
function getRuleProvider(options: ts.FormatCodeOptions) {
|
||||
// Share this between multiple formatters using the same options.
|
||||
// This represents the bulk of the space the formatter uses.
|
||||
let ruleProvider = new (<any>ts).formatting.RulesProvider();
|
||||
ruleProvider.ensureUpToDate(options);
|
||||
return ruleProvider;
|
||||
}
|
||||
|
||||
function applyEdits(text: string, edits: ts.TextChange[]): string {
|
||||
// Apply edits in reverse on the existing text
|
||||
let result = text;
|
||||
for (let i = edits.length - 1; i >= 0; i--) {
|
||||
let change = edits[i];
|
||||
let head = result.slice(0, change.span.start);
|
||||
let tail = result.slice(change.span.start + change.span.length);
|
||||
result = head + change.newText + tail;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getDefaultOptions(): ts.FormatCodeOptions {
|
||||
return {
|
||||
IndentSize: 4,
|
||||
TabSize: 4,
|
||||
NewLineCharacter: '\r\n',
|
||||
ConvertTabsToSpaces: true,
|
||||
IndentStyle: ts.IndentStyle.Block,
|
||||
|
||||
InsertSpaceAfterCommaDelimiter: true,
|
||||
InsertSpaceAfterSemicolonInForStatements: true,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true,
|
||||
PlaceOpenBraceOnNewLineForFunctions: false,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function createReplacer(data:string): (str:string)=>string {
|
||||
data = data || '';
|
||||
let rawDirectives = data.split(';');
|
||||
let directives: [RegExp,string][] = [];
|
||||
rawDirectives.forEach((rawDirective) => {
|
||||
if (rawDirective.length === 0) {
|
||||
return;
|
||||
}
|
||||
let pieces = rawDirective.split('=>');
|
||||
let findStr = pieces[0];
|
||||
let replaceStr = pieces[1];
|
||||
|
||||
findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&');
|
||||
findStr = '\\b' + findStr + '\\b';
|
||||
directives.push([new RegExp(findStr, 'g'), replaceStr]);
|
||||
});
|
||||
|
||||
return (str:string)=> {
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
str = str.replace(directives[i][0], directives[i][1]);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
}
|
||||
|
||||
function generateDeclarationFile(out:string, recipe:string): string {
|
||||
let lines = recipe.split(/\r\n|\n|\r/);
|
||||
let result = [];
|
||||
|
||||
|
||||
lines.forEach(line => {
|
||||
|
||||
let m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m1) {
|
||||
CURRENT_PROCESSING_RULE = line;
|
||||
let moduleId = m1[1];
|
||||
let sourceFile = getSourceFile(out, moduleId);
|
||||
if (!sourceFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let replacer = createReplacer(m1[2]);
|
||||
|
||||
let typeNames = m1[3].split(/,/);
|
||||
typeNames.forEach((typeName) => {
|
||||
typeName = typeName.trim();
|
||||
if (typeName.length === 0) {
|
||||
return;
|
||||
}
|
||||
let declaration = getTopLevelDeclaration(sourceFile, typeName);
|
||||
if (!declaration) {
|
||||
logErr('Cannot find type ' + typeName);
|
||||
return;
|
||||
}
|
||||
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration)));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m2) {
|
||||
CURRENT_PROCESSING_RULE = line;
|
||||
let moduleId = m2[1];
|
||||
let sourceFile = getSourceFile(out, moduleId);
|
||||
if (!sourceFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let replacer = createReplacer(m2[2]);
|
||||
|
||||
let typeNames = m2[3].split(/,/);
|
||||
let typesToExcludeMap: {[typeName:string]:boolean;} = {};
|
||||
let typesToExcludeArr: string[] = [];
|
||||
typeNames.forEach((typeName) => {
|
||||
typeName = typeName.trim();
|
||||
if (typeName.length === 0) {
|
||||
return;
|
||||
}
|
||||
typesToExcludeMap[typeName] = true;
|
||||
typesToExcludeArr.push(typeName);
|
||||
});
|
||||
|
||||
getAllTopLevelDeclarations(sourceFile).forEach((declaration) => {
|
||||
if (isDeclaration(declaration)) {
|
||||
if (typesToExcludeMap[declaration.name.text]) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// node is ts.VariableStatement
|
||||
let nodeText = getNodeText(sourceFile, declaration);
|
||||
for (let i = 0; i < typesToExcludeArr.length; i++) {
|
||||
if (nodeText.indexOf(typesToExcludeArr[i]) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration)));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
result.push(line);
|
||||
});
|
||||
|
||||
let resultTxt = result.join('\n');
|
||||
resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri');
|
||||
resultTxt = resultTxt.replace(/\bEvent</g, 'IEvent<');
|
||||
resultTxt = resultTxt.replace(/\bTPromise</g, 'Promise<');
|
||||
|
||||
resultTxt = format(resultTxt);
|
||||
|
||||
resultTxt = resultTxt.replace(/\r\n/g, '\n');
|
||||
return resultTxt;
|
||||
}
|
||||
|
||||
export function getFilesToWatch(out:string): string[] {
|
||||
let recipe = fs.readFileSync(RECIPE_PATH).toString();
|
||||
let lines = recipe.split(/\r\n|\n|\r/);
|
||||
let result = [];
|
||||
|
||||
lines.forEach(line => {
|
||||
|
||||
let m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m1) {
|
||||
let moduleId = m1[1];
|
||||
result.push(moduleIdToPath(out, moduleId));
|
||||
return;
|
||||
}
|
||||
|
||||
let m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
|
||||
if (m2) {
|
||||
let moduleId = m2[1];
|
||||
result.push(moduleIdToPath(out, moduleId));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface IMonacoDeclarationResult {
|
||||
content: string;
|
||||
filePath: string;
|
||||
isTheSame: boolean;
|
||||
}
|
||||
|
||||
export function run(out:string): IMonacoDeclarationResult {
|
||||
log('Starting monaco.d.ts generation');
|
||||
SOURCE_FILE_MAP = {};
|
||||
|
||||
let recipe = fs.readFileSync(RECIPE_PATH).toString();
|
||||
let result = generateDeclarationFile(out, recipe);
|
||||
|
||||
let currentContent = fs.readFileSync(DECLARATION_PATH).toString();
|
||||
log('Finished monaco.d.ts generation');
|
||||
|
||||
return {
|
||||
content: result,
|
||||
filePath: DECLARATION_PATH,
|
||||
isTheSame: currentContent === result
|
||||
};
|
||||
}
|
88
build/monaco/monaco.d.ts.recipe
Normal file
88
build/monaco/monaco.d.ts.recipe
Normal file
|
@ -0,0 +1,88 @@
|
|||
declare module monaco {
|
||||
|
||||
interface Thenable<R> {
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IEvent<T> {
|
||||
(listener: (e: T) => any, thisArg?: any): IDisposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that allows to emit and listen to typed events
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
constructor();
|
||||
event: Event<T>;
|
||||
fire(event?: T): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export enum Severity {
|
||||
Ignore = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
}
|
||||
|
||||
#include(vs/base/common/winjs.base.d.ts): TValueCallback, ProgressCallback, TPromise
|
||||
#include(vs/base/common/cancellation): CancellationTokenSource, CancellationToken
|
||||
#include(vs/base/common/uri): URI
|
||||
#include(vs/base/common/keyCodes): KeyCode, KeyMod
|
||||
#include(vs/base/common/htmlContent): IHTMLContentElementCode, IHTMLContentElement
|
||||
#include(vs/base/browser/keyboardEvent): IKeyboardEvent
|
||||
#include(vs/base/browser/mouseEvent): IMouseEvent
|
||||
#include(vs/editor/common/editorCommon): IScrollEvent
|
||||
#include(vs/editor/common/editorCommon): IPosition, IRange, ISelection
|
||||
#include(vs/editor/common/core/position): Position
|
||||
#include(vs/editor/common/core/range): Range
|
||||
#include(vs/editor/common/core/selection): Selection, SelectionDirection
|
||||
}
|
||||
|
||||
declare module monaco.editor {
|
||||
|
||||
#includeAll(vs/editor/browser/standalone/standaloneEditor;modes.=>languages.):
|
||||
#include(vs/editor/browser/standalone/standaloneCodeEditor): IEditorConstructionOptions, IDiffEditorConstructionOptions
|
||||
#include(vs/editor/browser/standalone/standaloneServices): IEditorOverrideServices
|
||||
#include(vs/platform/markers/common/markers): IMarkerData
|
||||
#include(vs/editor/browser/standalone/colorizer): IColorizerOptions, IColorizerElementOptions
|
||||
#include(vs/base/browser/ui/scrollbar/scrollableElementOptions): ScrollbarVisibility
|
||||
#include(vs/base/common/actions): IAction
|
||||
#includeAll(vs/editor/common/editorCommon;IMode=>languages.IMode): IPosition, IRange, ISelection, SelectionDirection, IScrollEvent
|
||||
#includeAll(vs/editor/browser/editorBrowser;editorCommon.=>):
|
||||
}
|
||||
|
||||
declare module monaco.languages {
|
||||
|
||||
#includeAll(vs/editor/browser/standalone/standaloneLanguages;modes.=>;editorCommon.=>editor.):
|
||||
#include(vs/editor/common/modes/languageConfigurationRegistry): CommentRule, LanguageConfiguration
|
||||
#include(vs/editor/common/modes/supports/onEnter): IndentationRule, OnEnterRule
|
||||
#include(vs/editor/common/modes/supports/electricCharacter): IBracketElectricCharacterContribution, IDocComment
|
||||
#includeAll(vs/editor/common/modes;editorCommon.IRange=>IRange;editorCommon.IPosition=>IPosition;editorCommon.=>editor.;IToken2=>IToken;ILineTokens2=>ILineTokens;IState2=>IState):
|
||||
#include(vs/editor/common/services/modeService): ILanguageExtensionPoint
|
||||
#includeAll(vs/editor/common/modes/monarch/monarchTypes):
|
||||
|
||||
}
|
||||
|
||||
declare module monaco.worker {
|
||||
|
||||
export interface IMirrorModel {
|
||||
uri: Uri;
|
||||
version: number;
|
||||
getText(): string;
|
||||
}
|
||||
|
||||
export var mirrorModels: IMirrorModel[];
|
||||
|
||||
}
|
15
build/monaco/package.json
Normal file
15
build/monaco/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "monaco-editor-core",
|
||||
"private": true,
|
||||
"version": "0.3.1",
|
||||
"description": "A browser based code editor",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/vscode"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Microsoft/vscode/issues"
|
||||
}
|
||||
}
|
25
build/npm/postinstall.js
Normal file
25
build/npm/postinstall.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const cp = require('child_process');
|
||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
const extensions = [
|
||||
'vscode-api-tests',
|
||||
'vscode-colorize-tests',
|
||||
'json',
|
||||
'configuration-editing',
|
||||
'typescript',
|
||||
'php',
|
||||
'javascript',
|
||||
'css'
|
||||
];
|
||||
|
||||
extensions.forEach(extension => {
|
||||
cp.spawnSync(npm, ['install'], {
|
||||
cwd: `extensions/${ extension }`,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
});
|
|
@ -6,5 +6,17 @@
|
|||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""]
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""]
|
||||
]
|
||||
}
|
|
@ -6,5 +6,17 @@
|
|||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""]
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""]
|
||||
]
|
||||
}
|
|
@ -2,6 +2,6 @@
|
|||
[{
|
||||
"name": "textmate/coffee-script.tmbundle",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"license": "TextMate Bundle License",
|
||||
"repositoryURL": "https://github.com/textmate/coffee-script.tmbundle"
|
||||
}]
|
||||
|
|
|
@ -7,5 +7,19 @@
|
|||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
}
|
9
extensions/configuration-editing/OSSREADME.json
Normal file
9
extensions/configuration-editing/OSSREADME.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
|
||||
|
||||
[{
|
||||
"name": "Microsoft/node-jsonc-parser",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"isProd": true,
|
||||
"repositoryURL": "https://github.com/Microsoft/node-jsonc-parser"
|
||||
}]
|
22
extensions/configuration-editing/package.json
Normal file
22
extensions/configuration-editing/package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "configuration-editing",
|
||||
"version": "0.0.1",
|
||||
"publisher": "vscode",
|
||||
"engines": {
|
||||
"vscode": "^1.0.0"
|
||||
},
|
||||
"categories": [
|
||||
"Languages", "Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:json"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:configuration-editing",
|
||||
"watch": "gulp watch-extension:configuration-editing"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^0.2.1"
|
||||
}
|
||||
}
|
27
extensions/configuration-editing/src/extension.ts
Normal file
27
extensions/configuration-editing/src/extension.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import {getLocation} from 'jsonc-parser';
|
||||
|
||||
export function activate(context) {
|
||||
|
||||
const commands = vscode.commands.getCommands(true);
|
||||
|
||||
//keybindings.json command-suggestions
|
||||
const disposable = vscode.languages.registerCompletionItemProvider({ pattern: '**/keybindings.json' }, {
|
||||
|
||||
provideCompletionItems(document, position, token) {
|
||||
const location = getLocation(document.getText(), document.offsetAt(position));
|
||||
if (location.path[1] === 'command') {
|
||||
return commands.then(ids => ids.map(id => new vscode.CompletionItem(id, vscode.CompletionItemKind.Value)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
10
extensions/configuration-editing/src/typings/ref.d.ts
vendored
Normal file
10
extensions/configuration-editing/src/typings/ref.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/typings/mocha.d.ts'/>
|
||||
/// <reference path='../../../../extensions/node.d.ts'/>
|
||||
/// <reference path='../../../../extensions/lib.core.d.ts'/>
|
||||
/// <reference path='../../../../extensions/declares.d.ts'/>
|
11
extensions/configuration-editing/tsconfig.json
Normal file
11
extensions/configuration-editing/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"noLib": true,
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"outDir": "./out"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -1,8 +1,31 @@
|
|||
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
|
||||
[{
|
||||
"name": "language-c",
|
||||
"version": "0.51.3",
|
||||
"license": "MIT",
|
||||
"repositoryURL": "https://github.com/atom/language-c",
|
||||
"description": "The files syntaxes/c.json and syntaxes/c++.json were derived from the Atom package https://atom.io/packages/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle."
|
||||
}]
|
||||
[
|
||||
{
|
||||
"name": "atom/language-c",
|
||||
"version": "0.51.3",
|
||||
"license": "MIT",
|
||||
"repositoryURL": "https://github.com/atom/language-c",
|
||||
"description": "The files syntaxes/c.json and syntaxes/c++.json were derived from the Atom package https://atom.io/packages/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle."
|
||||
},
|
||||
{
|
||||
"name": "textmate/c.tmbundle",
|
||||
"version": "0.0.0",
|
||||
"license": "TextMate Bundle License",
|
||||
"repositoryURL": "https://github.com/textmate/c.tmbundle",
|
||||
"licenseDetail": [
|
||||
"Copyright (c) textmate-c.tmbundle authors",
|
||||
"",
|
||||
"If not otherwise specified (see below), files in this repository fall under the following license:",
|
||||
"",
|
||||
"Permission to copy, use, modify, sell and distribute this",
|
||||
"software is granted. This software is provided \"as is\" without",
|
||||
"express or implied warranty, and with no claim as to its",
|
||||
"suitability for any purpose.",
|
||||
"",
|
||||
"An exception is made for files in readable text which contain their own license information,",
|
||||
"or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added",
|
||||
"to the base-name name of the original file, and an extension of txt, html, or similar. For example",
|
||||
"\"tidy\" is accompanied by \"tidy-license.txt\"."
|
||||
]
|
||||
}
|
||||
]
|
|
@ -7,5 +7,19 @@
|
|||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
{
|
||||
"id": "cpp",
|
||||
"extensions": [ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h", ".mm", ".ino" ],
|
||||
"extensions": [ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h", ".mm", ".ino", ".inl" ],
|
||||
"aliases": [ "C++", "Cpp", "cpp"],
|
||||
"configuration": "./cpp.configuration.json"
|
||||
}],
|
||||
|
|
29
extensions/css/.vscode/launch.json
vendored
Normal file
29
extensions/css/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/client/out",
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/client/out/test" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/client/out/test",
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
}
|
30
extensions/css/.vscode/tasks.json
vendored
Normal file
30
extensions/css/.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
{
|
||||
"version": "0.1.0",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// the command is a shell script
|
||||
"isShellCommand": true,
|
||||
|
||||
// show the output window only if unrecognized errors occur.
|
||||
"showOutput": "silent",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile"],
|
||||
|
||||
// The tsc compiler is started in watching mode
|
||||
"isWatching": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
83
extensions/css/client/src/colorDecorators.ts
Normal file
83
extensions/css/client/src/colorDecorators.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range} from 'vscode';
|
||||
|
||||
let decorationType: DecorationRenderOptions = {
|
||||
before: {
|
||||
contentText: ' ',
|
||||
border: 'solid 0.1em #000',
|
||||
margin: '0.1em 0.2em 0 0.2em',
|
||||
width: '0.8em',
|
||||
height: '0.8em'
|
||||
},
|
||||
dark: {
|
||||
before: {
|
||||
border: 'solid 0.1em #eee'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function activateColorDecorations(decoratorProvider: (uri: string) => Thenable<Range[]>, supportedLanguages: { [id: string]: boolean }): Disposable {
|
||||
|
||||
let disposables: Disposable[] = [];
|
||||
|
||||
let colorsDecorationType = window.createTextEditorDecorationType(decorationType);
|
||||
disposables.push(colorsDecorationType);
|
||||
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (activeEditor) {
|
||||
triggerUpdateDecorations();
|
||||
}
|
||||
|
||||
window.onDidChangeActiveTextEditor(editor => {
|
||||
activeEditor = editor;
|
||||
if (editor && supportedLanguages[activeEditor.document.languageId]) {
|
||||
triggerUpdateDecorations();
|
||||
}
|
||||
}, null, disposables);
|
||||
|
||||
workspace.onDidChangeTextDocument(event => {
|
||||
if (activeEditor && event.document === activeEditor.document && supportedLanguages[activeEditor.document.languageId]) {
|
||||
triggerUpdateDecorations();
|
||||
}
|
||||
}, null, disposables);
|
||||
|
||||
let timeout = null;
|
||||
function triggerUpdateDecorations() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(updateDecorations, 500);
|
||||
}
|
||||
|
||||
function updateDecorations() {
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
let document = activeEditor.document;
|
||||
if (!supportedLanguages[document.languageId]) {
|
||||
return;
|
||||
}
|
||||
let uri = activeEditor.document.uri.toString();
|
||||
decoratorProvider(uri).then(ranges => {
|
||||
|
||||
let decorations = ranges.map(range => {
|
||||
let color = document.getText(range);
|
||||
return <DecorationOptions>{
|
||||
range: range,
|
||||
renderOptions: {
|
||||
before: {
|
||||
backgroundColor: color
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
activeEditor.setDecorations(colorsDecorationType, decorations);
|
||||
});
|
||||
}
|
||||
return Disposable.from(...disposables);
|
||||
}
|
125
extensions/css/client/src/cssMain.ts
Normal file
125
extensions/css/client/src/cssMain.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
import {languages, window, commands, ExtensionContext} from 'vscode';
|
||||
import {LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, Range, TextEdit, Protocol2Code} from 'vscode-languageclient';
|
||||
import {activateColorDecorations} from './colorDecorators';
|
||||
|
||||
namespace ColorSymbolRequest {
|
||||
export const type: RequestType<string, Range[], any> = { get method() { return 'css/colorSymbols'; } };
|
||||
}
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
// The server is implemented in node
|
||||
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'cssServerMain.js'));
|
||||
// The debug options for the server
|
||||
let debugOptions = { execArgv: ['--nolazy', '--debug=6004'] };
|
||||
|
||||
// If the extension is launch in debug mode the debug server options are use
|
||||
// Otherwise the run options are used
|
||||
let serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector: ['css', 'less', 'scss'],
|
||||
synchronize: {
|
||||
configurationSection: ['css', 'scss', 'less']
|
||||
},
|
||||
initializationOptions: {
|
||||
}
|
||||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
let client = new LanguageClient('css', serverOptions, clientOptions);
|
||||
|
||||
let disposable = client.start();
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
context.subscriptions.push(disposable);
|
||||
|
||||
let colorRequestor = (uri: string) => {
|
||||
return client.sendRequest(ColorSymbolRequest.type, uri).then(ranges => ranges.map(Protocol2Code.asRange));
|
||||
};
|
||||
disposable = activateColorDecorations(colorRequestor, { css: true, scss: true, less: true });
|
||||
context.subscriptions.push(disposable);
|
||||
|
||||
languages.setLanguageConfiguration('css', {
|
||||
wordPattern: /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g,
|
||||
comments: {
|
||||
blockComment: ['/*', '*/']
|
||||
},
|
||||
brackets: [['{', '}'], ['[', ']'], ['(', ')']],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"', notIn: ['string'] },
|
||||
{ open: '\'', close: '\'', notIn: ['string'] }
|
||||
]
|
||||
});
|
||||
|
||||
languages.setLanguageConfiguration('less', {
|
||||
wordPattern: /(#?-?\d*\.\d\w*%?)|([@#!.:]?[\w-?]+%?)|[@#!.]/g,
|
||||
comments: {
|
||||
blockComment: ['/*', '*/'],
|
||||
lineComment: '//'
|
||||
},
|
||||
brackets: [['{', '}'], ['[', ']'], ['(', ')'], ['<', '>']],
|
||||
autoClosingPairs: [
|
||||
{ open: '"', close: '"', notIn: ['string', 'comment'] },
|
||||
{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
|
||||
{ open: '{', close: '}', notIn: ['string', 'comment'] },
|
||||
{ open: '[', close: ']', notIn: ['string', 'comment'] },
|
||||
{ open: '(', close: ')', notIn: ['string', 'comment'] },
|
||||
{ open: '<', close: '>', notIn: ['string', 'comment'] },
|
||||
]
|
||||
});
|
||||
|
||||
languages.setLanguageConfiguration('scss', {
|
||||
wordPattern: /(#?-?\d*\.\d\w*%?)|([@#!.:]?[\w-?]+%?)|[@#!.]/g,
|
||||
comments: {
|
||||
blockComment: ['/*', '*/'],
|
||||
lineComment: '//'
|
||||
},
|
||||
brackets: [['{', '}'], ['[', ']'], ['(', ')'], ['<', '>']],
|
||||
autoClosingPairs: [
|
||||
{ open: '"', close: '"', notIn: ['string', 'comment'] },
|
||||
{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
|
||||
{ open: '{', close: '}', notIn: ['string', 'comment'] },
|
||||
{ open: '[', close: ']', notIn: ['string', 'comment'] },
|
||||
{ open: '(', close: ')', notIn: ['string', 'comment'] },
|
||||
{ open: '<', close: '>', notIn: ['string', 'comment'] },
|
||||
]
|
||||
});
|
||||
|
||||
commands.registerCommand('_css.applyCodeAction', applyCodeAction);
|
||||
}
|
||||
|
||||
function applyCodeAction(uri: string, documentVersion: number, edits: TextEdit[]) {
|
||||
let textEditor = window.activeTextEditor;
|
||||
if (textEditor && textEditor.document.uri.toString() === uri) {
|
||||
if (textEditor.document.version !== documentVersion) {
|
||||
window.showInformationMessage(`CSS fix is outdated and can't be applied to the document.`);
|
||||
}
|
||||
textEditor.edit(mutator => {
|
||||
for (let edit of edits) {
|
||||
mutator.replace(Protocol2Code.asRange(edit.range), edit.newText);
|
||||
}
|
||||
}).then(success => {
|
||||
if (!success) {
|
||||
window.showErrorMessage('Failed to apply CSS fix to the document. Please consider opening an issue with steps to reproduce.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
11
extensions/css/client/src/typings/ref.d.ts
vendored
Normal file
11
extensions/css/client/src/typings/ref.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../../src/typings/mocha.d.ts'/>
|
||||
/// <reference path='../../../../../extensions/node.d.ts'/>
|
||||
/// <reference path='../../../../../extensions/lib.core.d.ts'/>
|
||||
/// <reference path='../../../../../extensions/declares.d.ts'/>
|
||||
/// <reference path='../../../node_modules/vscode-languageclient/lib/main.d.ts'/>
|
11
extensions/css/client/tsconfig.json
Normal file
11
extensions/css/client/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"noLib": true,
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"outDir": "./out"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -2,7 +2,20 @@
|
|||
"name": "css",
|
||||
"version": "0.1.0",
|
||||
"publisher": "vscode",
|
||||
"engines": { "vscode": "*" },
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onLanguage:css",
|
||||
"onLanguage:less",
|
||||
"onLanguage:scss",
|
||||
"onCommand:_css.applyCodeAction"
|
||||
],
|
||||
"main": "./client/out/cssMain",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:css-client && gulp compile-extension:css-server",
|
||||
"postinstall": "cd server && npm install"
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [{
|
||||
"id": "css",
|
||||
|
@ -18,6 +31,531 @@
|
|||
"snippets": [{
|
||||
"language": "css",
|
||||
"path": "./snippets/css.json"
|
||||
}]
|
||||
}],
|
||||
"configuration": {
|
||||
"allOf": [{
|
||||
"id": "css",
|
||||
"order": 20,
|
||||
"title": "CSS configuration",
|
||||
"allOf": [
|
||||
{
|
||||
"title": "Controls CSS validation and problem severities.",
|
||||
"properties": {
|
||||
"css.validate": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables or disables all validations"
|
||||
},
|
||||
"css.lint.compatibleVendorPrefixes": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties"
|
||||
},
|
||||
"css.lint.vendorPrefix": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "warning",
|
||||
"description": "When using a vendor-specific prefix also include the standard property"
|
||||
},
|
||||
"css.lint.duplicateProperties": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Do not use duplicate style definitions"
|
||||
},
|
||||
"css.lint.emptyRules": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "warning",
|
||||
"description": "Do not use empty rulesets"
|
||||
},
|
||||
"css.lint.importStatement": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Import statements do not load in parallel"
|
||||
},
|
||||
"css.lint.boxModel": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Do not use width or height when using padding or border"
|
||||
},
|
||||
"css.lint.universalSelector": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "The universal selector (*) is known to be slow"
|
||||
},
|
||||
"css.lint.zeroUnits": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "No unit for zero needed"
|
||||
},
|
||||
"css.lint.fontFaceProperties": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "warning",
|
||||
"description": "@font-face rule must define 'src' and 'font-family' properties"
|
||||
},
|
||||
"css.lint.hexColorLength": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "error",
|
||||
"description": "Hex colors must consist of three or six hex numbers"
|
||||
},
|
||||
"css.lint.argumentsInColorFunction": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "error",
|
||||
"description": "Invalid number of parameters"
|
||||
},
|
||||
"css.lint.unknownProperties": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "warning",
|
||||
"description": "Unknown property."
|
||||
},
|
||||
"css.lint.ieHack": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "IE hacks are only necessary when supporting IE7 and older"
|
||||
},
|
||||
"css.lint.unknownVendorSpecificProperties": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Unknown vendor specific property."
|
||||
},
|
||||
"css.lint.propertyIgnoredDueToDisplay": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "warning",
|
||||
"description": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect"
|
||||
},
|
||||
"css.lint.important": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored."
|
||||
},
|
||||
"css.lint.float": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes."
|
||||
},
|
||||
"css.lint.idSelector": {
|
||||
"type": "string",
|
||||
"enum": [ "ignore", "warning", "error"],
|
||||
"default": "ignore",
|
||||
"description": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "scss",
|
||||
"order": 24,
|
||||
"title": "SCSS (Sass) configuration",
|
||||
"allOf": [
|
||||
{
|
||||
"title": "Controls SCSS validation and problem severities.",
|
||||
"properties": {
|
||||
"scss.validate": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables or disables all validations"
|
||||
},
|
||||
"scss.lint.compatibleVendorPrefixes": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties"
|
||||
},
|
||||
"scss.lint.vendorPrefix": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "When using a vendor-specific prefix also include the standard property"
|
||||
},
|
||||
"scss.lint.duplicateProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Do not use duplicate style definitions"
|
||||
},
|
||||
"scss.lint.emptyRules": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Do not use empty rulesets"
|
||||
},
|
||||
"scss.lint.importStatement": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Import statements do not load in parallel"
|
||||
},
|
||||
"scss.lint.boxModel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Do not use width or height when using padding or border"
|
||||
},
|
||||
"scss.lint.universalSelector": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "The universal selector (*) is known to be slow"
|
||||
},
|
||||
"scss.lint.zeroUnits": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "No unit for zero needed"
|
||||
},
|
||||
"scss.lint.fontFaceProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "@font-face rule must define 'src' and 'font-family' properties"
|
||||
},
|
||||
"scss.lint.hexColorLength": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "error",
|
||||
"description": "Hex colors must consist of three or six hex numbers"
|
||||
},
|
||||
"scss.lint.argumentsInColorFunction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "error",
|
||||
"description": "Invalid number of parameters"
|
||||
},
|
||||
"scss.lint.unknownProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Unknown property."
|
||||
},
|
||||
"scss.lint.ieHack": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "IE hacks are only necessary when supporting IE7 and older"
|
||||
},
|
||||
"scss.lint.unknownVendorSpecificProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Unknown vendor specific property."
|
||||
},
|
||||
"scss.lint.propertyIgnoredDueToDisplay": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect"
|
||||
},
|
||||
"scss.lint.important": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored."
|
||||
},
|
||||
"scss.lint.float": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes."
|
||||
},
|
||||
"scss.lint.idSelector": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "less",
|
||||
"order": 22,
|
||||
"type": "object",
|
||||
"title": "LESS configuration",
|
||||
"allOf": [
|
||||
{
|
||||
"title": "Controls LESS validation and problem severities.",
|
||||
"properties": {
|
||||
"less.validate": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables or disables all validations"
|
||||
},
|
||||
"less.lint.compatibleVendorPrefixes": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties"
|
||||
},
|
||||
"less.lint.vendorPrefix": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "When using a vendor-specific prefix also include the standard property"
|
||||
},
|
||||
"less.lint.duplicateProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Do not use duplicate style definitions"
|
||||
},
|
||||
"less.lint.emptyRules": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Do not use empty rulesets"
|
||||
},
|
||||
"less.lint.importStatement": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Import statements do not load in parallel"
|
||||
},
|
||||
"less.lint.boxModel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Do not use width or height when using padding or border"
|
||||
},
|
||||
"less.lint.universalSelector": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "The universal selector (*) is known to be slow"
|
||||
},
|
||||
"less.lint.zeroUnits": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "No unit for zero needed"
|
||||
},
|
||||
"less.lint.fontFaceProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "@font-face rule must define 'src' and 'font-family' properties"
|
||||
},
|
||||
"less.lint.hexColorLength": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "error",
|
||||
"description": "Hex colors must consist of three or six hex numbers"
|
||||
},
|
||||
"less.lint.argumentsInColorFunction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "error",
|
||||
"description": "Invalid number of parameters"
|
||||
},
|
||||
"less.lint.unknownProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Unknown property."
|
||||
},
|
||||
"less.lint.ieHack": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "IE hacks are only necessary when supporting IE7 and older"
|
||||
},
|
||||
"less.lint.unknownVendorSpecificProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Unknown vendor specific property."
|
||||
},
|
||||
"less.lint.propertyIgnoredDueToDisplay": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "warning",
|
||||
"description": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect"
|
||||
},
|
||||
"less.lint.important": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored."
|
||||
},
|
||||
"less.lint.float": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes."
|
||||
},
|
||||
"less.lint.idSelector": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"default": "ignore",
|
||||
"description": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^2.2.1"
|
||||
}
|
||||
}
|
32
extensions/css/server/.vscode/launch.json
vendored
Normal file
32
extensions/css/server/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
// List of configurations. Add new configurations or edit existing ones.
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 6004,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/out"
|
||||
},
|
||||
{
|
||||
"name": "Unit Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/../../../node_modules/mocha/bin/_mocha",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
"--timeout",
|
||||
"999999",
|
||||
"--colors"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": null,
|
||||
"runtimeArgs": [],
|
||||
"env": {},
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/out"
|
||||
}
|
||||
]
|
||||
}
|
9
extensions/css/server/.vscode/tasks.json
vendored
Normal file
9
extensions/css/server/.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"command": "npm",
|
||||
"isShellCommand": true,
|
||||
"showOutput": "silent",
|
||||
"args": ["run", "watch"],
|
||||
"isWatching": true,
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
18
extensions/css/server/package.json
Normal file
18
extensions/css/server/package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "vscode-css-server",
|
||||
"description": "CSS/LESS/SCSS language server",
|
||||
"version": "0.0.1",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageserver": "^2.2.0",
|
||||
"vscode-nls": "^1.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:css-server",
|
||||
"watch": "gulp watch-extension:css-server"
|
||||
}
|
||||
}
|
85
extensions/css/server/src/cssLanguageService.ts
Normal file
85
extensions/css/server/src/cssLanguageService.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {TextDocument, Position, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
|
||||
Location, DocumentHighlight, CodeActionContext, Command, WorkspaceEdit} from 'vscode-languageserver';
|
||||
|
||||
import {Stylesheet} from './parser/cssNodes';
|
||||
import {Parser} from './parser/cssParser';
|
||||
import {CSSCompletion} from './services/cssCompletion';
|
||||
import {CSSHover} from './services/cssHover';
|
||||
import {CSSNavigation} from './services/cssNavigation';
|
||||
import {CSSCodeActions} from './services/cssCodeActions';
|
||||
import {CSSValidation} from './services/cssValidation';
|
||||
|
||||
import {SCSSParser} from './parser/scssParser';
|
||||
import {SCSSCompletion} from './services/scssCompletion';
|
||||
import {LESSParser} from './parser/lessParser';
|
||||
import {LESSCompletion} from './services/lessCompletion';
|
||||
|
||||
export interface LanguageService {
|
||||
configure(raw: LanguageSettings): void;
|
||||
doValidation(document: TextDocument, stylesheet: Stylesheet): Thenable<Diagnostic[]>;
|
||||
parseStylesheet(document: TextDocument): Stylesheet;
|
||||
doComplete(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<CompletionList>;
|
||||
doHover(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Hover>;
|
||||
findDefinition(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Location>;
|
||||
findReferences(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Location[]>;
|
||||
findDocumentHighlights(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<DocumentHighlight[]>;
|
||||
findDocumentSymbols(document: TextDocument, stylesheet: Stylesheet): Thenable<SymbolInformation[]>;
|
||||
doCodeActions(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): Thenable<Command[]>;
|
||||
findColorSymbols(document: TextDocument, stylesheet: Stylesheet): Thenable<Range[]>;
|
||||
doRename(document: TextDocument, position: Position, newName: string, stylesheet: Stylesheet): Thenable<WorkspaceEdit>;
|
||||
}
|
||||
|
||||
export interface LanguageSettings {
|
||||
validate?: boolean;
|
||||
lint?: any;
|
||||
}
|
||||
|
||||
let cssParser = new Parser();
|
||||
let cssCompletion = new CSSCompletion();
|
||||
let cssHover = new CSSHover();
|
||||
let cssValidation = new CSSValidation();
|
||||
let cssNavigation = new CSSNavigation();
|
||||
let cssCodeActions = new CSSCodeActions();
|
||||
|
||||
export function getCSSLanguageService() : LanguageService {
|
||||
return {
|
||||
configure: cssValidation.configure.bind(cssValidation),
|
||||
doValidation: cssValidation.doValidation.bind(cssValidation),
|
||||
parseStylesheet: cssParser.parseStylesheet.bind(cssParser),
|
||||
doComplete: cssCompletion.doComplete.bind(cssCompletion),
|
||||
doHover: cssHover.doHover.bind(cssHover),
|
||||
findDefinition: cssNavigation.findDefinition.bind(cssNavigation),
|
||||
findReferences: cssNavigation.findReferences.bind(cssNavigation),
|
||||
findDocumentHighlights: cssNavigation.findDocumentHighlights.bind(cssNavigation),
|
||||
findDocumentSymbols: cssNavigation.findDocumentSymbols.bind(cssNavigation),
|
||||
doCodeActions: cssCodeActions.doCodeActions.bind(cssCodeActions),
|
||||
findColorSymbols: cssNavigation.findColorSymbols.bind(cssNavigation),
|
||||
doRename: cssNavigation.doRename.bind(cssNavigation),
|
||||
};
|
||||
}
|
||||
|
||||
let scssParser = new SCSSParser();
|
||||
let scssCompletion = new SCSSCompletion();
|
||||
|
||||
export function getSCSSLanguageService() : LanguageService {
|
||||
let languageService = getCSSLanguageService();
|
||||
languageService.parseStylesheet = scssParser.parseStylesheet.bind(scssParser);
|
||||
languageService.doComplete = scssCompletion.doComplete.bind(scssCompletion);
|
||||
return languageService;
|
||||
}
|
||||
|
||||
let lessParser = new LESSParser();
|
||||
let lessCompletion = new LESSCompletion();
|
||||
|
||||
export function getLESSLanguageService() : LanguageService {
|
||||
let languageService = getCSSLanguageService();
|
||||
languageService.parseStylesheet = lessParser.parseStylesheet.bind(lessParser);
|
||||
languageService.doComplete = lessCompletion.doComplete.bind(lessCompletion);
|
||||
return languageService;
|
||||
}
|
187
extensions/css/server/src/cssServerMain.ts
Normal file
187
extensions/css/server/src/cssServerMain.ts
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
IPCMessageReader, IPCMessageWriter, createConnection, IConnection, Range,
|
||||
TextDocuments, TextDocument, InitializeParams, InitializeResult, RequestType
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import {getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService} from './cssLanguageService';
|
||||
import {getStylesheetCache} from './stylesheetCache';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
nls.config(process.env['VSCODE_NLS_CONFIG']);
|
||||
|
||||
namespace ColorSymbolRequest {
|
||||
export const type: RequestType<string, Range[], any> = { get method() { return 'css/colorSymbols'; } };
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
css: LanguageSettings;
|
||||
less: LanguageSettings;
|
||||
scss: LanguageSettings;
|
||||
}
|
||||
|
||||
// Create a connection for the server. The connection uses for
|
||||
// stdin / stdout for message passing
|
||||
let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
|
||||
|
||||
console.log = connection.console.log.bind(connection.console);
|
||||
console.error = connection.console.error.bind(connection.console);
|
||||
|
||||
// Create a simple text document manager. The text document manager
|
||||
// supports full document sync only
|
||||
let documents: TextDocuments = new TextDocuments();
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
let stylesheets = getStylesheetCache(10, 60, document => getLanguageService(document).parseStylesheet(document));
|
||||
documents.onDidClose(e => {
|
||||
stylesheets.onDocumentRemoved(e.document);
|
||||
});
|
||||
connection.onShutdown(() => {
|
||||
stylesheets.dispose();
|
||||
});
|
||||
|
||||
// After the server has started the client sends an initilize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilites.
|
||||
connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
return {
|
||||
capabilities: {
|
||||
// Tell the client that the server works in FULL text document sync mode
|
||||
textDocumentSync: documents.syncKind,
|
||||
completionProvider: { resolveProvider: false },
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
referencesProvider: true,
|
||||
definitionProvider: true,
|
||||
documentHighlightProvider: true,
|
||||
codeActionProvider: true,
|
||||
renameProvider: true
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let languageServices : { [id:string]: LanguageService} = {
|
||||
css: getCSSLanguageService(),
|
||||
scss: getSCSSLanguageService(),
|
||||
less: getLESSLanguageService()
|
||||
};
|
||||
|
||||
function getLanguageService(document: TextDocument) {
|
||||
let service = languageServices[document.languageId];
|
||||
if (!service) {
|
||||
connection.console.log('Document type is ' + document.languageId + ', using css instead.');
|
||||
service = languageServices['css'];
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
updateConfiguration(<Settings>change.settings);
|
||||
});
|
||||
|
||||
function updateConfiguration(settings: Settings) {
|
||||
for (let languageId in languageServices) {
|
||||
languageServices[languageId].configure(settings[languageId]);
|
||||
}
|
||||
// Revalidate any open text documents
|
||||
documents.all().forEach(triggerValidation);
|
||||
}
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
});
|
||||
|
||||
let pendingValidationRequests : {[uri:string]:number} = {};
|
||||
const validationDelayMs = 200;
|
||||
documents.onDidClose(e => {
|
||||
let request = pendingValidationRequests[e.document.uri];
|
||||
if (request) {
|
||||
clearTimeout(request);
|
||||
delete pendingValidationRequests[e.document.uri];
|
||||
}
|
||||
});
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
let request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
clearTimeout(request);
|
||||
}
|
||||
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
validateTextDocument(textDocument);
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
function validateTextDocument(textDocument: TextDocument): void {
|
||||
let stylesheet = stylesheets.getStylesheet(textDocument);
|
||||
getLanguageService(textDocument).doValidation(textDocument, stylesheet).then(diagnostics => {
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
});
|
||||
}
|
||||
|
||||
connection.onCompletion(textDocumentPosition => {
|
||||
let document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheet);
|
||||
});
|
||||
|
||||
connection.onHover(textDocumentPosition => {
|
||||
let document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
let styleSheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet);
|
||||
});
|
||||
|
||||
connection.onDocumentSymbol(documentSymbolParams => {
|
||||
let document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).findDocumentSymbols(document, stylesheet);
|
||||
});
|
||||
|
||||
connection.onDefinition(documentSymbolParams => {
|
||||
let document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).findDefinition(document, documentSymbolParams.position, stylesheet);
|
||||
});
|
||||
|
||||
connection.onDocumentHighlight(documentSymbolParams => {
|
||||
let document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).findDocumentHighlights(document, documentSymbolParams.position, stylesheet);
|
||||
});
|
||||
|
||||
connection.onReferences(referenceParams => {
|
||||
let document = documents.get(referenceParams.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet);
|
||||
});
|
||||
|
||||
connection.onCodeAction(codeActionParams => {
|
||||
let document = documents.get(codeActionParams.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet);
|
||||
});
|
||||
|
||||
connection.onRequest(ColorSymbolRequest.type, uri => {
|
||||
let document = documents.get(uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).findColorSymbols(document, stylesheet);
|
||||
});
|
||||
|
||||
connection.onRenameRequest(renameParameters => {
|
||||
let document = documents.get(renameParameters.textDocument.uri);
|
||||
let stylesheet = stylesheets.getStylesheet(document);
|
||||
return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet);
|
||||
});
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
7
extensions/css/server/src/data/browsers.d.ts
vendored
Normal file
7
extensions/css/server/src/data/browsers.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export declare var data: any;
|
||||
export declare var descriptions: any;
|
11307
extensions/css/server/src/data/browsers.js
Normal file
11307
extensions/css/server/src/data/browsers.js
Normal file
File diff suppressed because it is too large
Load diff
9770
extensions/css/server/src/data/buildscripts/css-schema.xml
Normal file
9770
extensions/css/server/src/data/buildscripts/css-schema.xml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,392 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
/* global __dirname */
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var xml2js = require('xml2js');
|
||||
var os = require('os');
|
||||
var util = require('util');
|
||||
|
||||
// keep in sync with data from language facts
|
||||
var colors = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
grey: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370d8',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#d87093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rebeccapurple: '#663399',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
|
||||
var otherColors = {
|
||||
"ActiveBorder": "Active window border.",
|
||||
"ActiveCaption": "Active window caption.",
|
||||
"AppWorkspace": "Background color of multiple document interface.",
|
||||
"Background": "Desktop background.",
|
||||
"ButtonFace": "The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
||||
"ButtonHighlight": "The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
||||
"ButtonShadow": "The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
|
||||
"ButtonText": "Text on push buttons.",
|
||||
"CaptionText": "Text in caption, size box, and scrollbar arrow box.",
|
||||
"currentColor": "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.",
|
||||
"GrayText": "Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
|
||||
"Highlight": "Item(s) selected in a control.",
|
||||
"HighlightText": "Text of item(s) selected in a control.",
|
||||
"InactiveBorder": "Inactive window border.",
|
||||
"InactiveCaption": "Inactive window caption.",
|
||||
"InactiveCaptionText": "Color of text in an inactive caption.",
|
||||
"InfoBackground": "Background color for tooltip controls.",
|
||||
"InfoText": "Text color for tooltip controls.",
|
||||
"Menu": "Menu background.",
|
||||
"MenuText": "Text in menus.",
|
||||
"Scrollbar": "Scroll bar gray area.",
|
||||
"ThreeDDarkShadow": "The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
||||
"ThreeDFace": "The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
||||
"ThreeDHighlight": "The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
||||
"ThreeDLightShadow": "The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
||||
"ThreeDShadow": "The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
|
||||
"transparent": "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.",
|
||||
"Window": "Window background.",
|
||||
"WindowFrame": "Window frame.",
|
||||
"WindowText": "Text in windows.",
|
||||
"none": "",
|
||||
|
||||
//ignore these
|
||||
"-webkit-activelink": "",
|
||||
"-webkit-focus-ring-color": '',
|
||||
"-webkit-link": '',
|
||||
"-webkit-text": ''
|
||||
};
|
||||
|
||||
|
||||
function clone(obj) {
|
||||
var copy = {};
|
||||
for (var i in obj) {
|
||||
copy[i] = obj[i];
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
function getProperties(obj) {
|
||||
var res = [];
|
||||
for (var i in obj) {
|
||||
res.push(i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getValues(valArr, restriction, ruleName) {
|
||||
if (!Array.isArray(valArr)) {
|
||||
if (valArr.$) {
|
||||
valArr = [ valArr ];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
var vals = valArr.map(function (v) {
|
||||
return {
|
||||
name: v.$.name,
|
||||
desc: v.desc,
|
||||
browsers: v.$.browsers !== 'all' ? v.$.browsers : void 0
|
||||
};
|
||||
}).filter(function (v) {
|
||||
if (v.browsers === 'none') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (restriction.indexOf('color') !== -1) {
|
||||
|
||||
var colorsCopy = clone(colors);
|
||||
var otherColorsCopy = clone(otherColors);
|
||||
|
||||
var moreColors = {};
|
||||
|
||||
vals = vals.filter(function (v) {
|
||||
if (typeof colorsCopy[v.name] === 'string') {
|
||||
delete colorsCopy[v.name];
|
||||
return false;
|
||||
}
|
||||
if (typeof otherColorsCopy[v.name] === 'string') {
|
||||
delete otherColorsCopy[v.name];
|
||||
return false;
|
||||
}
|
||||
moreColors[v.name] = v.desc;
|
||||
return true;
|
||||
});
|
||||
var notCovered = [];
|
||||
for (var i in colorsCopy) {
|
||||
notCovered.push(i);
|
||||
}
|
||||
for (var i in otherColorsCopy) {
|
||||
notCovered.push(i);
|
||||
}
|
||||
if (notCovered.length > 0) {
|
||||
console.log('***' + ruleName + ' uncovered: ' + notCovered.length); // + ' - ' + JSON.stringify(notCovered));
|
||||
}
|
||||
|
||||
if (restriction === 'color') {
|
||||
var properties = getProperties(moreColors);
|
||||
|
||||
console.log('---' + ruleName + ' others : ' + properties.length); // + ' - ' + JSON.stringify(properties));
|
||||
}
|
||||
}
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
function internalizeDescriptions(entries) {
|
||||
var descriptions = {};
|
||||
var conflicts = {};
|
||||
entries.forEach(function (e) {
|
||||
if (e.values) {
|
||||
e.values.forEach(function (d) {
|
||||
if (!d.desc) {
|
||||
conflicts[d.name] = true;
|
||||
return;
|
||||
}
|
||||
var existing = descriptions[d.name];
|
||||
if (existing) {
|
||||
if (existing !== d.desc) {
|
||||
conflicts[d.name] = true;
|
||||
}
|
||||
}
|
||||
descriptions[d.name] = d.desc;
|
||||
});
|
||||
}
|
||||
});
|
||||
entries.forEach(function (e) {
|
||||
if (e.values) {
|
||||
e.values.forEach(function (d) {
|
||||
if (!conflicts[d.name]) {
|
||||
delete d.desc;
|
||||
} else {
|
||||
delete descriptions[d.name];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
function toSource(object, keyName) {
|
||||
if (!object.css[keyName]) {
|
||||
return [];
|
||||
}
|
||||
var result = [];
|
||||
var entryArr = object.css[keyName].entry;
|
||||
entryArr.forEach(function (e) {
|
||||
if (e.$.browsers === 'none') {
|
||||
return;
|
||||
}
|
||||
var data = {
|
||||
name: e.$.name,
|
||||
desc: e.desc,
|
||||
browsers: e.$.browsers !== 'all' ? e.$.browsers : void 0
|
||||
};
|
||||
if (e.$.restriction) {
|
||||
data.restriction= e.$.restriction;
|
||||
}
|
||||
if (e.values) {
|
||||
data.values= getValues(e.values.value, data.restriction || '', data.name);
|
||||
}
|
||||
|
||||
result.push(data);
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
var parser = new xml2js.Parser({explicitArray : false});
|
||||
var schemaFileName= 'css-schema.xml';
|
||||
fs.readFile(path.resolve(__dirname, schemaFileName), function(err, data) {
|
||||
parser.parseString(data, function (err, result) {
|
||||
|
||||
//console.log(util.inspect(result, {depth: null})); //Work
|
||||
|
||||
var atdirectives = toSource(result, 'atDirectives');
|
||||
var pseudoclasses = toSource(result, 'pseudoClasses');
|
||||
var pseudoelements = toSource(result, 'pseudoElements');
|
||||
var properties = toSource(result, 'properties');
|
||||
|
||||
var descriptions = internalizeDescriptions([].concat(atdirectives, pseudoclasses, pseudoelements, properties));
|
||||
|
||||
var resultObject = {
|
||||
css: {
|
||||
atdirectives: atdirectives,
|
||||
pseudoclasses: pseudoclasses,
|
||||
pseudoelements: pseudoelements,
|
||||
properties: properties
|
||||
}
|
||||
};
|
||||
|
||||
var output = [
|
||||
'/*---------------------------------------------------------------------------------------------',
|
||||
' * Copyright (c) Microsoft Corporation. All rights reserved.',
|
||||
' * Licensed under the MIT License. See License.txt in the project root for license information.',
|
||||
' *--------------------------------------------------------------------------------------------*/',
|
||||
'// file generated from ' + schemaFileName + ' using css-exclude_generate_browserjs.js',
|
||||
'',
|
||||
'(function (factory) {',
|
||||
'\tif (typeof module === "object" && typeof module.exports === "object") {',
|
||||
'\t\tvar v = factory(require, exports); if (v !== undefined) module.exports = v;',
|
||||
'\t} else if (typeof define === "function" && define.amd) {',
|
||||
'\t\tdefine(["require", "exports"], factory);',
|
||||
'\t}',
|
||||
'})(function (require, exports) {',
|
||||
'\texports.data = ' + JSON.stringify(resultObject, null, '\t') + ';',
|
||||
'\texports.descriptions = ' + JSON.stringify(descriptions, null, '\t') + ';',
|
||||
'});'
|
||||
];
|
||||
|
||||
var outputPath = path.resolve(__dirname, '../browsers.js');
|
||||
console.log('Writing to: ' + outputPath);
|
||||
var content = output.join(os.EOL);
|
||||
fs.writeFileSync(outputPath, content);
|
||||
console.log('Done');
|
||||
});
|
||||
});
|
51
extensions/css/server/src/parser/cssErrors.ts
Normal file
51
extensions/css/server/src/parser/cssErrors.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from './cssNodes';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CSSIssueType implements nodes.IRule {
|
||||
id: string;
|
||||
message: string;
|
||||
|
||||
public constructor(id: string, message: string) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
export let ParseError = {
|
||||
NumberExpected: new CSSIssueType('css-numberexpected', localize('expected.number', "number expected")),
|
||||
ConditionExpected: new CSSIssueType('css-conditionexpected', localize('expected.condt', "condition expected")),
|
||||
RuleOrSelectorExpected: new CSSIssueType('css-ruleorselectorexpected', localize('expected.ruleorselector', "at-rule or selector expected")),
|
||||
DotExpected: new CSSIssueType('css-dotexpected', localize('expected.dot', "dot expected")),
|
||||
ColonExpected: new CSSIssueType('css-colonexpected', localize('expected.colon', "colon expected")),
|
||||
SemiColonExpected: new CSSIssueType('css-semicolonexpected', localize('expected.semicolon', "semi-colon expected")),
|
||||
TermExpected: new CSSIssueType('css-termexpected', localize('expected.term', "term expected")),
|
||||
ExpressionExpected: new CSSIssueType('css-expressionexpected', localize('expected.expression', "expression expected")),
|
||||
OperatorExpected: new CSSIssueType('css-operatorexpected', localize('expected.operator', "operator expected")),
|
||||
IdentifierExpected: new CSSIssueType('css-identifierexpected', localize('expected.ident', "identifier expected")),
|
||||
PercentageExpected: new CSSIssueType('css-percentageexpected', localize('expected.percentage', "percentage expected")),
|
||||
URIOrStringExpected: new CSSIssueType('css-uriorstringexpected', localize('expected.uriorstring', "uri or string expected")),
|
||||
URIExpected: new CSSIssueType('css-uriexpected', localize('expected.uri', "URI expected")),
|
||||
VariableNameExpected: new CSSIssueType('css-varnameexpected', localize('expected.varname', "variable name expected")),
|
||||
VariableValueExpected: new CSSIssueType('css-varvalueexpected', localize('expected.varvalue', "variable value expected")),
|
||||
PropertyValueExpected: new CSSIssueType('css-propertyvalueexpected', localize('expected.propvalue', "property value expected")),
|
||||
LeftCurlyExpected: new CSSIssueType('css-lcurlyexpected', localize('expected.lcurly', "{ expected")),
|
||||
RightCurlyExpected: new CSSIssueType('css-rcurlyexpected', localize('expected.rcurly', "} expected")),
|
||||
LeftSquareBracketExpected: new CSSIssueType('css-rbracketexpected', localize('expected.lsquare', "[ expected")),
|
||||
RightSquareBracketExpected: new CSSIssueType('css-lbracketexpected', localize('expected.rsquare', "] expected")),
|
||||
LeftParenthesisExpected: new CSSIssueType('css-lparentexpected', localize('expected.lparen', "( expected")),
|
||||
RightParenthesisExpected: new CSSIssueType('css-rparentexpected', localize('expected.rparent', ") expected")),
|
||||
CommaExpected: new CSSIssueType('css-commaexpected', localize('expected.comma', "comma expected")),
|
||||
PageDirectiveOrDeclarationExpected: new CSSIssueType('css-pagedirordeclexpected', localize('expected.pagedirordecl', "page directive or declaraton expected")),
|
||||
UnknownAtRule: new CSSIssueType('css-unknownatrule', localize('unknown.atrule', "at-rule unknown")),
|
||||
UnknownKeyword: new CSSIssueType('css-unknownkeyword', localize('unknown.keyword', "unknown keyword")),
|
||||
SelectorExpected: new CSSIssueType('css-selectorexpected', localize('expected.selector', "selector expected")),
|
||||
StringLiteralExpected: new CSSIssueType('css-stringliteralexpected', localize('expected.stringliteral', "string literal expected")),
|
||||
};
|
1582
extensions/css/server/src/parser/cssNodes.ts
Normal file
1582
extensions/css/server/src/parser/cssNodes.ts
Normal file
File diff suppressed because it is too large
Load diff
1019
extensions/css/server/src/parser/cssParser.ts
Normal file
1019
extensions/css/server/src/parser/cssParser.ts
Normal file
File diff suppressed because it is too large
Load diff
654
extensions/css/server/src/parser/cssScanner.ts
Normal file
654
extensions/css/server/src/parser/cssScanner.ts
Normal file
|
@ -0,0 +1,654 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export enum TokenType {
|
||||
Ident,
|
||||
AtKeyword,
|
||||
String,
|
||||
BadString,
|
||||
BadUri,
|
||||
Hash,
|
||||
Num,
|
||||
Percentage,
|
||||
Dimension,
|
||||
URI,
|
||||
UnicodeRange,
|
||||
CDO,
|
||||
CDC,
|
||||
Colon,
|
||||
SemiColon,
|
||||
CurlyL,
|
||||
CurlyR,
|
||||
ParenthesisL,
|
||||
ParenthesisR,
|
||||
BracketL,
|
||||
BracketR,
|
||||
Whitespace,
|
||||
Includes,
|
||||
Dashmatch,
|
||||
SubstringOperator,
|
||||
PrefixOperator,
|
||||
SuffixOperator,
|
||||
Delim,
|
||||
EMS,
|
||||
EXS,
|
||||
Length,
|
||||
Angle,
|
||||
Time,
|
||||
Freq,
|
||||
Exclamation,
|
||||
Resolution,
|
||||
Comma,
|
||||
Charset,
|
||||
EscapedJavaScript,
|
||||
BadEscapedJavaScript,
|
||||
Comment,
|
||||
SingleLineComment,
|
||||
EOF,
|
||||
CustomToken
|
||||
}
|
||||
|
||||
export interface IToken {
|
||||
type: TokenType;
|
||||
text: string;
|
||||
offset: number;
|
||||
len: number;
|
||||
}
|
||||
|
||||
export class MultiLineStream {
|
||||
|
||||
private source: string;
|
||||
private len: number;
|
||||
private position: number;
|
||||
|
||||
constructor(source: string) {
|
||||
this.source = source;
|
||||
this.len = source.length;
|
||||
this.position = 0;
|
||||
}
|
||||
|
||||
public substring(from: number, to: number = this.position): string {
|
||||
return this.source.substring(from, to);
|
||||
}
|
||||
|
||||
public eos(): boolean {
|
||||
return this.len <= this.position;
|
||||
}
|
||||
|
||||
public pos(): number {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public goBackTo(pos: number): void {
|
||||
this.position = pos;
|
||||
}
|
||||
|
||||
public goBack(n: number): void {
|
||||
this.position -= n;
|
||||
}
|
||||
|
||||
public advance(n: number): void {
|
||||
this.position += n;
|
||||
}
|
||||
|
||||
public nextChar(): number {
|
||||
return this.source.charCodeAt(this.position++) || 0;
|
||||
}
|
||||
|
||||
public peekChar(n: number = 0): number {
|
||||
return this.source.charCodeAt(this.position + n) || 0;
|
||||
}
|
||||
|
||||
public lookbackChar(n: number = 0): number {
|
||||
return this.source.charCodeAt(this.position - n) || 0;
|
||||
}
|
||||
|
||||
public advanceIfChar(ch: number): boolean {
|
||||
if (ch === this.source.charCodeAt(this.position)) {
|
||||
this.position++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public advanceIfChars(ch: number[]): boolean {
|
||||
let i: number;
|
||||
if (this.position + ch.length > this.source.length) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < ch.length; i++) {
|
||||
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.advance(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
public advanceWhileChar(condition: (ch: number) => boolean): number {
|
||||
let posNow = this.position;
|
||||
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
|
||||
this.position++;
|
||||
}
|
||||
return this.position - posNow;
|
||||
}
|
||||
}
|
||||
|
||||
const _a = 'a'.charCodeAt(0);
|
||||
const _e = 'e'.charCodeAt(0);
|
||||
const _f = 'f'.charCodeAt(0);
|
||||
const _i = 'i'.charCodeAt(0);
|
||||
const _l = 'l'.charCodeAt(0);
|
||||
const _p = 'p'.charCodeAt(0);
|
||||
const _r = 'r'.charCodeAt(0);
|
||||
const _u = 'u'.charCodeAt(0);
|
||||
const _x = 'x'.charCodeAt(0);
|
||||
const _z = 'z'.charCodeAt(0);
|
||||
const _A = 'A'.charCodeAt(0);
|
||||
const _E = 'E'.charCodeAt(0);
|
||||
const _F = 'F'.charCodeAt(0);
|
||||
const _I = 'I'.charCodeAt(0);
|
||||
const _L = 'L'.charCodeAt(0);
|
||||
const _P = 'P'.charCodeAt(0);
|
||||
const _R = 'R'.charCodeAt(0);
|
||||
const _U = 'U'.charCodeAt(0);
|
||||
const _X = 'X'.charCodeAt(0);
|
||||
const _Z = 'Z'.charCodeAt(0);
|
||||
const _0 = '0'.charCodeAt(0);
|
||||
const _9 = '9'.charCodeAt(0);
|
||||
const _TLD = '~'.charCodeAt(0);
|
||||
const _HAT = '^'.charCodeAt(0);
|
||||
const _EQS = '='.charCodeAt(0);
|
||||
const _PIP = '|'.charCodeAt(0);
|
||||
const _MIN = '-'.charCodeAt(0);
|
||||
const _USC = '_'.charCodeAt(0);
|
||||
const _PRC = '%'.charCodeAt(0);
|
||||
const _MUL = '*'.charCodeAt(0);
|
||||
const _LPA = '('.charCodeAt(0);
|
||||
const _RPA = ')'.charCodeAt(0);
|
||||
const _LAN = '<'.charCodeAt(0);
|
||||
const _RAN = '>'.charCodeAt(0);
|
||||
const _ATS = '@'.charCodeAt(0);
|
||||
const _HSH = '#'.charCodeAt(0);
|
||||
const _DLR = '$'.charCodeAt(0);
|
||||
const _BSL = '\\'.charCodeAt(0);
|
||||
const _FSL = '/'.charCodeAt(0);
|
||||
const _NWL = '\n'.charCodeAt(0);
|
||||
const _CAR = '\r'.charCodeAt(0);
|
||||
const _LFD = '\f'.charCodeAt(0);
|
||||
const _DQO = '"'.charCodeAt(0);
|
||||
const _SQO = '\''.charCodeAt(0);
|
||||
const _WSP = ' '.charCodeAt(0);
|
||||
const _TAB = '\t'.charCodeAt(0);
|
||||
const _SEM = ';'.charCodeAt(0);
|
||||
const _COL = ':'.charCodeAt(0);
|
||||
const _CUL = '{'.charCodeAt(0);
|
||||
const _CUR = '}'.charCodeAt(0);
|
||||
const _BRL = '['.charCodeAt(0);
|
||||
const _BRR = ']'.charCodeAt(0);
|
||||
const _CMA = ','.charCodeAt(0);
|
||||
const _DOT = '.'.charCodeAt(0);
|
||||
const _BNG = '!'.charCodeAt(0);
|
||||
|
||||
const _url = [_u, _U, _r, _R, _l, _L, _LPA, _LPA];
|
||||
const _url_prefix = [_u, _U, _r, _R, _l, _L, _MIN, _MIN, _p, _P, _r, _R, _e, _E, _f, _F, _i, _I, _x, _X, _LPA, _LPA];
|
||||
|
||||
const staticTokenTable: { [code: number]: TokenType; } = {};
|
||||
staticTokenTable[_SEM] = TokenType.SemiColon;
|
||||
staticTokenTable[_COL] = TokenType.Colon;
|
||||
staticTokenTable[_CUL] = TokenType.CurlyL;
|
||||
staticTokenTable[_CUR] = TokenType.CurlyR;
|
||||
staticTokenTable[_BRR] = TokenType.BracketR;
|
||||
staticTokenTable[_BRL] = TokenType.BracketL;
|
||||
staticTokenTable[_LPA] = TokenType.ParenthesisL;
|
||||
staticTokenTable[_RPA] = TokenType.ParenthesisR;
|
||||
staticTokenTable[_CMA] = TokenType.Comma;
|
||||
|
||||
const staticUnitTable: { [code: number]: TokenType; } = {};
|
||||
staticUnitTable['em'] = TokenType.EMS;
|
||||
staticUnitTable['ex'] = TokenType.EXS;
|
||||
staticUnitTable['px'] = TokenType.Length;
|
||||
staticUnitTable['cm'] = TokenType.Length;
|
||||
staticUnitTable['mm'] = TokenType.Length;
|
||||
staticUnitTable['in'] = TokenType.Length;
|
||||
staticUnitTable['pt'] = TokenType.Length;
|
||||
staticUnitTable['pc'] = TokenType.Length;
|
||||
staticUnitTable['deg'] = TokenType.Angle;
|
||||
staticUnitTable['rad'] = TokenType.Angle;
|
||||
staticUnitTable['grad'] = TokenType.Angle;
|
||||
staticUnitTable['ms'] = TokenType.Time;
|
||||
staticUnitTable['s'] = TokenType.Time;
|
||||
staticUnitTable['hz'] = TokenType.Freq;
|
||||
staticUnitTable['khz'] = TokenType.Freq;
|
||||
staticUnitTable['%'] = TokenType.Percentage;
|
||||
staticUnitTable['dpi'] = TokenType.Resolution;
|
||||
staticUnitTable['dpcm'] = TokenType.Resolution;
|
||||
|
||||
export class Scanner {
|
||||
|
||||
public stream: MultiLineStream;
|
||||
public ignoreComment = true;
|
||||
public ignoreWhitespace = true;
|
||||
|
||||
public setSource(input: string): void {
|
||||
this.stream = new MultiLineStream(input);
|
||||
}
|
||||
|
||||
public finishToken(offset: number, type: TokenType, text?: string): IToken {
|
||||
return {
|
||||
offset: offset,
|
||||
len: this.stream.pos() - offset,
|
||||
type: type,
|
||||
text: text || this.stream.substring(offset)
|
||||
};
|
||||
}
|
||||
|
||||
public substring(offset: number, len: number): string {
|
||||
return this.stream.substring(offset, offset + len);
|
||||
}
|
||||
|
||||
public pos(): number {
|
||||
return this.stream.pos();
|
||||
}
|
||||
|
||||
public goBackTo(pos: number): void {
|
||||
this.stream.goBackTo(pos);
|
||||
}
|
||||
|
||||
public scan(): IToken {
|
||||
// processes all whitespaces and comments
|
||||
let triviaToken = this.trivia();
|
||||
if (triviaToken !== null) {
|
||||
return triviaToken;
|
||||
}
|
||||
|
||||
let offset = this.stream.pos();
|
||||
|
||||
// End of file/input
|
||||
if (this.stream.eos()) {
|
||||
return this.finishToken(offset, TokenType.EOF);
|
||||
}
|
||||
|
||||
// CDO <!--
|
||||
if (this.stream.advanceIfChars([_LAN, _BNG, _MIN, _MIN])) {
|
||||
return this.finishToken(offset, TokenType.CDO);
|
||||
}
|
||||
|
||||
// CDC -->
|
||||
if (this.stream.advanceIfChars([_MIN, _MIN, _RAN])) {
|
||||
return this.finishToken(offset, TokenType.CDC);
|
||||
}
|
||||
|
||||
// URL
|
||||
let tokenType = this._url();
|
||||
if (tokenType !== null) {
|
||||
return this.finishToken(offset, tokenType);
|
||||
}
|
||||
|
||||
let content: string[] = [];
|
||||
if (this.ident(content)) {
|
||||
return this.finishToken(offset, TokenType.Ident, content.join(''));
|
||||
}
|
||||
|
||||
// at-keyword
|
||||
if (this.stream.advanceIfChar(_ATS)) {
|
||||
content = ['@'];
|
||||
if (this._name(content)) {
|
||||
let keywordText = content.join('');
|
||||
if (keywordText === '@charset') {
|
||||
return this.finishToken(offset, TokenType.Charset, keywordText);
|
||||
}
|
||||
return this.finishToken(offset, TokenType.AtKeyword, keywordText);
|
||||
} else {
|
||||
return this.finishToken(offset, TokenType.Delim);
|
||||
}
|
||||
}
|
||||
|
||||
// hash
|
||||
if (this.stream.advanceIfChar(_HSH)) {
|
||||
content = ['#'];
|
||||
if (this._name(content)) {
|
||||
return this.finishToken(offset, TokenType.Hash, content.join(''));
|
||||
} else {
|
||||
return this.finishToken(offset, TokenType.Delim);
|
||||
}
|
||||
}
|
||||
|
||||
// Important
|
||||
if (this.stream.advanceIfChar(_BNG)) {
|
||||
return this.finishToken(offset, TokenType.Exclamation);
|
||||
}
|
||||
|
||||
// Numbers
|
||||
if (this._number()) {
|
||||
|
||||
let pos = this.stream.pos();
|
||||
content = [this.stream.substring(offset, pos)];
|
||||
if (this.stream.advanceIfChar(_PRC)) {
|
||||
// Percentage 43%
|
||||
return this.finishToken(offset, TokenType.Percentage);
|
||||
} else if (this.ident(content)) {
|
||||
let dim = this.stream.substring(pos).toLowerCase();
|
||||
tokenType = <TokenType>staticUnitTable[dim];
|
||||
if (typeof tokenType !== 'undefined') {
|
||||
// Known dimension 43px
|
||||
return this.finishToken(offset, tokenType, content.join(''));
|
||||
} else {
|
||||
// Unknown dimension 43ft
|
||||
return this.finishToken(offset, TokenType.Dimension, content.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
return this.finishToken(offset, TokenType.Num);
|
||||
}
|
||||
|
||||
// String, BadString
|
||||
content = [];
|
||||
tokenType = this._string(content);
|
||||
if (tokenType !== null) {
|
||||
return this.finishToken(offset, tokenType, content.join(''));
|
||||
}
|
||||
|
||||
// single character tokens
|
||||
tokenType = <TokenType>staticTokenTable[this.stream.peekChar()];
|
||||
if (typeof tokenType !== 'undefined') {
|
||||
this.stream.advance(1);
|
||||
return this.finishToken(offset, tokenType);
|
||||
}
|
||||
|
||||
// includes ~=
|
||||
if (this.stream.peekChar(0) === _TLD && this.stream.peekChar(1) === _EQS) {
|
||||
this.stream.advance(2);
|
||||
return this.finishToken(offset, TokenType.Includes);
|
||||
}
|
||||
|
||||
// DashMatch |=
|
||||
if (this.stream.peekChar(0) === _PIP && this.stream.peekChar(1) === _EQS) {
|
||||
this.stream.advance(2);
|
||||
return this.finishToken(offset, TokenType.Dashmatch);
|
||||
}
|
||||
|
||||
// Substring operator *=
|
||||
if (this.stream.peekChar(0) === _MUL && this.stream.peekChar(1) === _EQS) {
|
||||
this.stream.advance(2);
|
||||
return this.finishToken(offset, TokenType.SubstringOperator);
|
||||
}
|
||||
|
||||
// Substring operator ^=
|
||||
if (this.stream.peekChar(0) === _HAT && this.stream.peekChar(1) === _EQS) {
|
||||
this.stream.advance(2);
|
||||
return this.finishToken(offset, TokenType.PrefixOperator);
|
||||
}
|
||||
|
||||
// Substring operator $=
|
||||
if (this.stream.peekChar(0) === _DLR && this.stream.peekChar(1) === _EQS) {
|
||||
this.stream.advance(2);
|
||||
return this.finishToken(offset, TokenType.SuffixOperator);
|
||||
}
|
||||
|
||||
// Delim
|
||||
this.stream.nextChar();
|
||||
return this.finishToken(offset, TokenType.Delim);
|
||||
}
|
||||
|
||||
private _matchWordAnyCase(characters: number[]): boolean {
|
||||
let index = 0;
|
||||
this.stream.advanceWhileChar((ch: number) => {
|
||||
let result = characters[index] === ch || characters[index + 1] === ch;
|
||||
if (result) {
|
||||
index += 2;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
if (index === characters.length) {
|
||||
return true;
|
||||
} else {
|
||||
this.stream.goBack(index / 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected trivia(): IToken {
|
||||
while (true) {
|
||||
let offset = this.stream.pos();
|
||||
if (this._whitespace()) {
|
||||
if (!this.ignoreWhitespace) {
|
||||
return this.finishToken(offset, TokenType.Whitespace);
|
||||
}
|
||||
} else if (this.comment()) {
|
||||
if (!this.ignoreComment) {
|
||||
return this.finishToken(offset, TokenType.Comment);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected comment(): boolean {
|
||||
if (this.stream.advanceIfChars([_FSL, _MUL])) {
|
||||
let success = false, hot = false;
|
||||
this.stream.advanceWhileChar((ch) => {
|
||||
if (hot && ch === _FSL) {
|
||||
success = true;
|
||||
return false;
|
||||
}
|
||||
hot = ch === _MUL;
|
||||
return true;
|
||||
});
|
||||
if (success) {
|
||||
this.stream.advance(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _number(): boolean {
|
||||
let npeek = 0, ch: number;
|
||||
if (this.stream.peekChar() === _DOT) {
|
||||
npeek = 1;
|
||||
}
|
||||
ch = this.stream.peekChar(npeek);
|
||||
if (ch >= _0 && ch <= _9) {
|
||||
this.stream.advance(npeek + 1);
|
||||
this.stream.advanceWhileChar((ch) => {
|
||||
return ch >= _0 && ch <= _9 || npeek === 0 && ch === _DOT;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _newline(result: string[]): boolean {
|
||||
let ch = this.stream.peekChar();
|
||||
switch (ch) {
|
||||
case _CAR:
|
||||
case _LFD:
|
||||
case _NWL:
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
if (ch === _CAR && this.stream.advanceIfChar(_NWL)) {
|
||||
result.push('\n');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _escape(result: string[], includeNewLines?: boolean): boolean {
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch === _BSL) {
|
||||
this.stream.advance(1);
|
||||
ch = this.stream.peekChar();
|
||||
let hexNumCount = 0;
|
||||
while (hexNumCount < 6 && (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F)) {
|
||||
this.stream.advance(1);
|
||||
ch = this.stream.peekChar();
|
||||
hexNumCount++;
|
||||
}
|
||||
if (hexNumCount > 0) {
|
||||
try {
|
||||
let hexVal = parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16);
|
||||
if (hexVal) {
|
||||
result.push(String.fromCharCode(hexVal));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// optional whitespace or new line, not part of result text
|
||||
if (ch === _WSP || ch === _TAB) {
|
||||
this.stream.advance(1);
|
||||
} else {
|
||||
this._newline([]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (ch !== _CAR && ch !== _LFD && ch !== _NWL) {
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
return true;
|
||||
} else if (includeNewLines) {
|
||||
return this._newline(result);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _stringChar(closeQuote: number, result: string[]) {
|
||||
// not closeQuote, not backslash, not newline
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) {
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
private _string(result: string[]): TokenType {
|
||||
if (this.stream.peekChar() === _SQO || this.stream.peekChar() === _DQO) {
|
||||
let closeQuote = this.stream.nextChar();
|
||||
result.push(String.fromCharCode(closeQuote));
|
||||
|
||||
while (this._stringChar(closeQuote, result) || this._escape(result, true)) {
|
||||
// loop
|
||||
}
|
||||
|
||||
if (this.stream.peekChar() === closeQuote) {
|
||||
this.stream.nextChar();
|
||||
result.push(String.fromCharCode(closeQuote));
|
||||
return TokenType.String;
|
||||
} else {
|
||||
return TokenType.BadString;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _url(): TokenType {
|
||||
if (this._matchWordAnyCase(_url) || this._matchWordAnyCase(_url_prefix)) {
|
||||
this._whitespace();
|
||||
let tokenType = TokenType.URI, stringType = this._string([]);
|
||||
if (stringType === TokenType.BadString) {
|
||||
tokenType = TokenType.BadUri;
|
||||
|
||||
} else if (stringType === null) {
|
||||
this.stream.advanceWhileChar((ch) => {
|
||||
return ch !== _RPA;
|
||||
});
|
||||
tokenType = TokenType.URI;
|
||||
}
|
||||
this._whitespace();
|
||||
if (this.stream.advanceIfChar(_RPA)) {
|
||||
return tokenType;
|
||||
} else {
|
||||
return TokenType.BadUri;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _whitespace(): boolean {
|
||||
let n = this.stream.advanceWhileChar((ch) => {
|
||||
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
|
||||
});
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
private _name(result: string[]): boolean {
|
||||
let matched = false;
|
||||
while (this._identChar(result) || this._escape(result)) {
|
||||
matched = true;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
protected ident(result: string[]): boolean {
|
||||
let pos = this.stream.pos();
|
||||
let hasMinus = this._minus(result);
|
||||
if (hasMinus && this._minus(result) /* -- */) {
|
||||
let hasContent = false;
|
||||
while (this._identChar(result) || this._escape(result)) {
|
||||
hasContent = true;
|
||||
}
|
||||
if (hasContent) {
|
||||
return true;
|
||||
}
|
||||
} else if (this._identFirstChar(result) || this._escape(result)) {
|
||||
while (this._identChar(result) || this._escape(result)) {
|
||||
// loop
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.stream.goBackTo(pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
private _identFirstChar(result: string[]): boolean {
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch === _USC || // _
|
||||
ch >= _a && ch <= _z || // a-z
|
||||
ch >= _A && ch <= _Z || // A-Z
|
||||
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private _minus(result: string[]): boolean {
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch === _MIN) {
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _identChar(result: string[]): boolean {
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch === _USC || // _
|
||||
ch === _MIN || // -
|
||||
ch >= _a && ch <= _z || // a-z
|
||||
ch >= _A && ch <= _Z || // A-Z
|
||||
ch >= _0 && ch <= _9 || // 0/9
|
||||
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
|
||||
this.stream.advance(1);
|
||||
result.push(String.fromCharCode(ch));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
362
extensions/css/server/src/parser/cssSymbolScope.ts
Normal file
362
extensions/css/server/src/parser/cssSymbolScope.ts
Normal file
|
@ -0,0 +1,362 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from './cssNodes';
|
||||
import {findFirst} from '../utils/arrays';
|
||||
|
||||
export class Scope {
|
||||
|
||||
public parent: Scope;
|
||||
public children: Scope[];
|
||||
|
||||
public offset: number;
|
||||
public length: number;
|
||||
|
||||
private symbols: Symbol[];
|
||||
|
||||
constructor(offset: number, length: number) {
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.symbols = [];
|
||||
|
||||
this.parent = null;
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
public addChild(scope: Scope): void {
|
||||
this.children.push(scope);
|
||||
scope.setParent(this);
|
||||
}
|
||||
|
||||
public setParent(scope: Scope): void {
|
||||
this.parent = scope;
|
||||
}
|
||||
|
||||
public findScope(offset: number, length: number = 0): Scope {
|
||||
if (this.offset <= offset && this.offset + this.length > offset + length || this.offset === offset && this.length === length) {
|
||||
return this.findInScope(offset, length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private findInScope(offset: number, length: number = 0): Scope {
|
||||
// find the first scope child that has an offset larger than offset + length
|
||||
let end = offset + length;
|
||||
let idx = findFirst(this.children, s => s.offset > end);
|
||||
if (idx === 0) {
|
||||
// all scopes have offsets larger than our end
|
||||
return this;
|
||||
}
|
||||
|
||||
let res = this.children[idx - 1];
|
||||
if (res.offset <= offset && res.offset + res.length >= offset + length) {
|
||||
return res.findInScope(offset, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public addSymbol(symbol: Symbol): void {
|
||||
this.symbols.push(symbol);
|
||||
}
|
||||
|
||||
public getSymbol(name: string, type: nodes.ReferenceType): Symbol {
|
||||
for (let index = 0; index < this.symbols.length; index++) {
|
||||
let symbol = this.symbols[index];
|
||||
if (symbol.name === name && symbol.type === type) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getSymbols(): Symbol[] {
|
||||
return this.symbols;
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalScope extends Scope {
|
||||
|
||||
constructor() {
|
||||
super(0, Number.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class Symbol {
|
||||
|
||||
public name: string;
|
||||
public type: nodes.ReferenceType;
|
||||
public node: nodes.Node;
|
||||
|
||||
constructor(name: string, node: nodes.Node, type: nodes.ReferenceType) {
|
||||
this.name = name;
|
||||
this.node = node;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScopeBuilder implements nodes.IVisitor {
|
||||
|
||||
public scope: Scope;
|
||||
|
||||
constructor(scope: Scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
private addSymbol(node: nodes.Node, name: string, type: nodes.ReferenceType): void {
|
||||
if (node.offset !== -1) {
|
||||
let current = this.scope.findScope(node.offset, node.length);
|
||||
current.addSymbol(new Symbol(name, node, type));
|
||||
}
|
||||
}
|
||||
|
||||
private addScope(node: nodes.Node): Scope {
|
||||
if (node.offset !== -1) {
|
||||
let current = this.scope.findScope(node.offset, node.length);
|
||||
if (current.offset !== node.offset || current.length !== node.length) { // scope already known?
|
||||
let newScope = new Scope(node.offset, node.length);
|
||||
current.addChild(newScope);
|
||||
return newScope;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private addSymbolToChildScope(scopeNode: nodes.Node, node: nodes.Node, name: string, type: nodes.ReferenceType): void {
|
||||
if (scopeNode && scopeNode.offset !== -1) {
|
||||
let current = this.addScope(scopeNode); // create the scope or gets the existing one
|
||||
current.addSymbol(new Symbol(name, node, type));
|
||||
}
|
||||
}
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
switch (node.type) {
|
||||
case nodes.NodeType.Keyframe:
|
||||
this.addSymbol(node, (<nodes.Keyframe>node).getName(), nodes.ReferenceType.Keyframe);
|
||||
return true;
|
||||
case nodes.NodeType.Declaration:
|
||||
return this.visitDeclarationNode(<nodes.Declaration>node);
|
||||
case nodes.NodeType.VariableDeclaration:
|
||||
return this.visitVariableDeclarationNode(<nodes.VariableDeclaration>node);
|
||||
case nodes.NodeType.Ruleset:
|
||||
return this.visitRuleSet(<nodes.RuleSet>node);
|
||||
case nodes.NodeType.MixinDeclaration:
|
||||
this.addSymbol(node, (<nodes.MixinDeclaration>node).getName(), nodes.ReferenceType.Mixin);
|
||||
return true;
|
||||
case nodes.NodeType.FunctionDeclaration:
|
||||
this.addSymbol(node, (<nodes.FunctionDeclaration>node).getName(), nodes.ReferenceType.Function);
|
||||
return true;
|
||||
case nodes.NodeType.FunctionParameter: {
|
||||
// parameters are part of the body scope
|
||||
let scopeNode = (<nodes.BodyDeclaration>node.getParent()).getDeclarations();
|
||||
if (scopeNode) {
|
||||
this.addSymbolToChildScope(scopeNode, node, (<nodes.FunctionParameter>node).getName(), nodes.ReferenceType.Variable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case nodes.NodeType.Declarations:
|
||||
this.addScope(node);
|
||||
return true;
|
||||
case nodes.NodeType.For:
|
||||
case nodes.NodeType.Each: {
|
||||
let forOrEachNode = <nodes.ForStatement | nodes.EachStatement>node;
|
||||
let scopeNode = forOrEachNode.getDeclarations();
|
||||
if (scopeNode) {
|
||||
this.addSymbolToChildScope(scopeNode, forOrEachNode.variable, forOrEachNode.variable.getName(), nodes.ReferenceType.Variable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public visitRuleSet(node: nodes.RuleSet): boolean {
|
||||
let current = this.scope.findScope(node.offset, node.length);
|
||||
node.getSelectors().getChildren().forEach((child) => {
|
||||
if (child instanceof nodes.Selector) {
|
||||
if (child.getChildren().length === 1) { // only selectors with a single element can be extended
|
||||
current.addSymbol(new Symbol(child.getChild(0).getText(), child, nodes.ReferenceType.Rule));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public visitVariableDeclarationNode(node: nodes.VariableDeclaration): boolean {
|
||||
this.addSymbol(node, (<nodes.VariableDeclaration>node).getName(), nodes.ReferenceType.Variable);
|
||||
return true;
|
||||
}
|
||||
|
||||
public visitDeclarationNode(node: nodes.Declaration): boolean {
|
||||
if (Symbols.isCssVariable(node.getProperty().getIdentifier())) {
|
||||
this.addCSSVariable(node.getProperty(), node.getProperty().getName(), nodes.ReferenceType.Variable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private addCSSVariable(node: nodes.Node, name: string, type: nodes.ReferenceType): void {
|
||||
if (node.offset !== -1) {
|
||||
let globalScope = this.getGlobalScope(node, name, type);
|
||||
globalScope.addSymbol(new Symbol(name, node, type));
|
||||
}
|
||||
}
|
||||
|
||||
private getGlobalScope(node: nodes.Node, name: string, type: nodes.ReferenceType): Scope {
|
||||
let current = this.scope.findScope(node.offset, node.length);
|
||||
while (current.parent !== null) {
|
||||
current = current.parent;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
export class Symbols {
|
||||
|
||||
private global: Scope;
|
||||
|
||||
constructor(node: nodes.Node) {
|
||||
this.global = new GlobalScope();
|
||||
node.accept(new ScopeBuilder(this.global));
|
||||
}
|
||||
|
||||
public findSymbolsAtOffset(offset: number, referenceType: nodes.ReferenceType): Symbol[] {
|
||||
let scope = this.global.findScope(offset, 0);
|
||||
let result: Symbol[] = [];
|
||||
let names: { [name: string]: boolean } = {};
|
||||
while (scope) {
|
||||
let symbols = scope.getSymbols();
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
let symbol = symbols[i];
|
||||
if (symbol.node.offset <= offset && symbol.type === referenceType && !names[symbol.name]) {
|
||||
result.push(symbol);
|
||||
names[symbol.name] = true;
|
||||
}
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private internalFindSymbol(node: nodes.Node, referenceTypes: nodes.ReferenceType[]): Symbol {
|
||||
let scopeNode = node;
|
||||
if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) {
|
||||
scopeNode = (<nodes.BodyDeclaration>node.parent.getParent()).getDeclarations();
|
||||
}
|
||||
if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) {
|
||||
let funcId = (<nodes.Function>node.parent.getParent()).getIdentifier();
|
||||
if (funcId) {
|
||||
let functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]);
|
||||
if (functionSymbol) {
|
||||
scopeNode = (<nodes.FunctionDeclaration>functionSymbol.node).getDeclarations();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!scopeNode) {
|
||||
return null;
|
||||
}
|
||||
let name = node.getText();
|
||||
let scope = this.global.findScope(scopeNode.offset, scopeNode.length);
|
||||
while (scope) {
|
||||
for (let index = 0; index < referenceTypes.length; index++) {
|
||||
let type = referenceTypes[index];
|
||||
let symbol = scope.getSymbol(name, type);
|
||||
if (symbol) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private evaluateReferenceTypes(node: nodes.Node): nodes.ReferenceType[] {
|
||||
if (node instanceof nodes.Identifier) {
|
||||
let referenceTypes = (<nodes.Identifier>node).referenceTypes;
|
||||
if (referenceTypes) {
|
||||
return referenceTypes;
|
||||
} else {
|
||||
if (Symbols.isCssVariable(node)) {
|
||||
return [nodes.ReferenceType.Variable];
|
||||
}
|
||||
// are a reference to a keyframe?
|
||||
let decl = nodes.getParentDeclaration(node);
|
||||
if (decl) {
|
||||
let propertyName = decl.getNonPrefixedPropertyName();
|
||||
if ((propertyName === 'animation' || propertyName === 'animation-name')
|
||||
&& decl.getValue() && decl.getValue().offset === node.offset) {
|
||||
return [nodes.ReferenceType.Keyframe];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node instanceof nodes.Variable) {
|
||||
return [nodes.ReferenceType.Variable];
|
||||
}
|
||||
let selector = node.findParent(nodes.NodeType.Selector);
|
||||
if (selector) {
|
||||
return [nodes.ReferenceType.Rule];
|
||||
}
|
||||
let extendsRef = <nodes.ExtendsReference>node.findParent(nodes.NodeType.ExtendsReference);
|
||||
if (extendsRef) {
|
||||
return [nodes.ReferenceType.Rule];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public findSymbolFromNode(node: nodes.Node): Symbol {
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
while (node.type === nodes.NodeType.Interpolation) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
let referenceTypes = this.evaluateReferenceTypes(node);
|
||||
if (referenceTypes) {
|
||||
return this.internalFindSymbol(node, referenceTypes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public matchesSymbol(node: nodes.Node, symbol: Symbol): boolean {
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
while (node.type === nodes.NodeType.Interpolation) {
|
||||
node = node.getParent();
|
||||
}
|
||||
if (symbol.name.length !== node.length || symbol.name !== node.getText()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let referenceTypes = this.evaluateReferenceTypes(node);
|
||||
if (!referenceTypes || referenceTypes.indexOf(symbol.type) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let nodeSymbol = this.internalFindSymbol(node, referenceTypes);
|
||||
return nodeSymbol === symbol;
|
||||
}
|
||||
|
||||
|
||||
public findSymbol(name: string, type: nodes.ReferenceType, offset: number): Symbol {
|
||||
let scope = this.global.findScope(offset);
|
||||
while (scope) {
|
||||
let symbol = scope.getSymbol(name, type);
|
||||
if (symbol) {
|
||||
return symbol;
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static isCssVariable(identifier: nodes.Identifier): boolean {
|
||||
return /^--/.test(identifier.getText());
|
||||
}
|
||||
}
|
395
extensions/css/server/src/parser/lessParser.ts
Normal file
395
extensions/css/server/src/parser/lessParser.ts
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as lessScanner from './lessScanner';
|
||||
import {TokenType} from './cssScanner';
|
||||
import * as cssParser from './cssParser';
|
||||
import * as nodes from './cssNodes';
|
||||
import {ParseError} from './cssErrors';
|
||||
|
||||
/// <summary>
|
||||
/// A parser for LESS
|
||||
/// http://lesscss.org/
|
||||
/// </summary>
|
||||
export class LESSParser extends cssParser.Parser {
|
||||
|
||||
public constructor() {
|
||||
super(new lessScanner.LESSScanner());
|
||||
}
|
||||
|
||||
public _parseStylesheetStatement(): nodes.Node {
|
||||
return this._tryParseMixinDeclaration() || super._parseStylesheetStatement() || this._parseVariableDeclaration();
|
||||
}
|
||||
|
||||
public _parseImport(): nodes.Node {
|
||||
let node = <nodes.Import>this.create(nodes.Import);
|
||||
if (!this.accept(TokenType.AtKeyword, '@import') && !this.accept(TokenType.AtKeyword, '@import-once') /* deprecated in less 1.4.1 */) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// less 1.4.1: @import (css) "lib"
|
||||
if (this.accept(TokenType.ParenthesisL)) {
|
||||
if (!this.accept(TokenType.Ident)) {
|
||||
return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]);
|
||||
}
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.SemiColon]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.URI) && !this.accept(TokenType.String)) {
|
||||
return this.finish(node, ParseError.URIOrStringExpected, [TokenType.SemiColon]);
|
||||
}
|
||||
|
||||
node.setMedialist(this._parseMediaList());
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMediaQuery(resyncStopToken: TokenType[]): nodes.Node {
|
||||
let node = <nodes.MediaQuery>super._parseMediaQuery(resyncStopToken);
|
||||
if (!node) {
|
||||
let node = <nodes.MediaQuery>this.create(nodes.MediaQuery);
|
||||
if (node.addChild(this._parseVariable())) {
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public _parseVariableDeclaration(panic: TokenType[] = []): nodes.VariableDeclaration {
|
||||
let node = <nodes.VariableDeclaration>this.create(nodes.VariableDeclaration);
|
||||
|
||||
let mark = this.mark();
|
||||
if (!node.setVariable(this._parseVariable())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.Colon, ':')) {
|
||||
node.colonPosition = this.prevToken.offset;
|
||||
if (!node.setValue(this._parseExpr())) {
|
||||
return <nodes.VariableDeclaration>this.finish(node, ParseError.VariableValueExpected, [], panic);
|
||||
}
|
||||
} else {
|
||||
this.restoreAtMark(mark);
|
||||
return null; // at keyword, but no ':', not a variable declaration but some at keyword
|
||||
}
|
||||
|
||||
if (this.peek(TokenType.SemiColon)) {
|
||||
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
||||
}
|
||||
|
||||
return <nodes.VariableDeclaration>this.finish(node);
|
||||
}
|
||||
|
||||
public _parseVariable(): nodes.Variable {
|
||||
let node = <nodes.Variable>this.create(nodes.Variable);
|
||||
let mark = this.mark();
|
||||
while (this.accept(TokenType.Delim, '@')) {
|
||||
if (this.hasWhitespace()) {
|
||||
this.restoreAtMark(mark);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!this.accept(TokenType.AtKeyword)) {
|
||||
this.restoreAtMark(mark);
|
||||
return null;
|
||||
}
|
||||
return <nodes.Variable>node;
|
||||
}
|
||||
|
||||
public _parseTerm(): nodes.Term {
|
||||
let term = super._parseTerm();
|
||||
if (term) { return term; }
|
||||
|
||||
term = <nodes.Term>this.create(nodes.Term);
|
||||
if (term.setExpression(this._parseVariable()) ||
|
||||
term.setExpression(this._parseEscaped())) {
|
||||
|
||||
return <nodes.Term>this.finish(term);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseEscaped(): nodes.Node {
|
||||
let node = this.createNode(nodes.NodeType.EscapedValue);
|
||||
if (this.accept(TokenType.EscapedJavaScript) ||
|
||||
this.accept(TokenType.BadEscapedJavaScript)) {
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.Delim, '~')) {
|
||||
return this.finish(node, this.accept(TokenType.String) ? null : ParseError.TermExpected);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseOperator(): nodes.Node {
|
||||
let node = this._parseGuardOperator();
|
||||
if (node) {
|
||||
return node;
|
||||
} else {
|
||||
return super._parseOperator();
|
||||
}
|
||||
}
|
||||
|
||||
public _parseGuardOperator(): nodes.Node {
|
||||
let node = this.createNode(nodes.NodeType.Operator);
|
||||
if (this.accept(TokenType.Delim, '>')) {
|
||||
this.accept(TokenType.Delim, '=');
|
||||
return node;
|
||||
} else if (this.accept(TokenType.Delim, '=')) {
|
||||
this.accept(TokenType.Delim, '<');
|
||||
return node;
|
||||
} else if (this.accept(TokenType.Delim, '<')) {
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseRuleSetDeclaration(): nodes.Node {
|
||||
if (this.peek(TokenType.AtKeyword)) {
|
||||
return this._parseKeyframe()
|
||||
|| this._parseMedia()
|
||||
|| this._parseVariableDeclaration(); // Variable declarations
|
||||
}
|
||||
return this._tryParseMixinDeclaration()
|
||||
|| this._tryParseRuleset(true) // nested ruleset
|
||||
|| this._parseMixinReference() // less mixin reference
|
||||
|| this._parseExtend() // less extend declaration
|
||||
|| super._parseRuleSetDeclaration(); // try css ruleset declaration as the last option
|
||||
}
|
||||
|
||||
public _parseSimpleSelectorBody(): nodes.Node {
|
||||
return this._parseSelectorCombinator() || super._parseSimpleSelectorBody();
|
||||
}
|
||||
|
||||
public _parseSelectorCombinator(): nodes.Node {
|
||||
let node = this.createNode(nodes.NodeType.SelectorCombinator);
|
||||
if (this.accept(TokenType.Delim, '&')) {
|
||||
while (!this.hasWhitespace() && (this.accept(TokenType.Delim, '-') || node.addChild(this._parseIdent()) || this.accept(TokenType.Delim, '&'))) {
|
||||
// support &-foo
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseSelectorIdent(): nodes.Node {
|
||||
return this._parseIdent() || this._parseSelectorInterpolation();
|
||||
}
|
||||
|
||||
public _parseSelectorInterpolation(): nodes.Node {
|
||||
// Selector interpolation; old: ~"@{name}", new: @{name}
|
||||
let node = this.createNode(nodes.NodeType.SelectorInterpolation);
|
||||
if (this.accept(TokenType.Delim, '~')) {
|
||||
if (!this.hasWhitespace() && (this.accept(TokenType.String) || this.accept(TokenType.BadString))) {
|
||||
return this.finish(node);
|
||||
}
|
||||
return this.finish(node, ParseError.StringLiteralExpected);
|
||||
} else if (this.accept(TokenType.Delim, '@')) {
|
||||
if (this.hasWhitespace() || !this.accept(TokenType.CurlyL)) {
|
||||
return this.finish(node, ParseError.LeftCurlyExpected);
|
||||
}
|
||||
if (!node.addChild(this._parseIdent())) {
|
||||
return this.finish(node, ParseError.IdentifierExpected);
|
||||
}
|
||||
if (!this.accept(TokenType.CurlyR)) {
|
||||
return this.finish(node, ParseError.RightCurlyExpected);
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _tryParseMixinDeclaration(): nodes.Node {
|
||||
if (!this.peek(TokenType.Delim, '.')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let mark = this.mark();
|
||||
let node = <nodes.MixinDeclaration>this.create(nodes.MixinDeclaration);
|
||||
|
||||
if (!node.setIdentifier(this._parseMixinDeclarationIdentifier()) || !this.accept(TokenType.ParenthesisL)) {
|
||||
this.restoreAtMark(mark);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.getParameters().addChild(this._parseMixinParameter())) {
|
||||
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
|
||||
if (!node.getParameters().addChild(this._parseMixinParameter())) {
|
||||
return this.finish(node, ParseError.IdentifierExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected);
|
||||
}
|
||||
node.setGuard(this._parseGuard());
|
||||
|
||||
if (!this.peek(TokenType.CurlyL)) {
|
||||
this.restoreAtMark(mark);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
||||
}
|
||||
|
||||
public _parseMixinDeclarationIdentifier(): nodes.Identifier {
|
||||
let identifier = <nodes.Identifier>this.create(nodes.Identifier); // identifier should contain dot
|
||||
this.consumeToken(); // .
|
||||
if (this.hasWhitespace() || !this.accept(TokenType.Ident)) {
|
||||
return null;
|
||||
}
|
||||
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
|
||||
return this.finish(identifier);
|
||||
}
|
||||
|
||||
public _parseExtend(): nodes.Node {
|
||||
if (!this.peek(TokenType.Delim, '&')) {
|
||||
return null;
|
||||
}
|
||||
let mark = this.mark();
|
||||
|
||||
let node = <nodes.ExtendsReference>this.create(nodes.ExtendsReference);
|
||||
this.consumeToken(); // &
|
||||
if (this.hasWhitespace() || !this.accept(TokenType.Colon) || !this.accept(TokenType.Ident, 'extend')) {
|
||||
this.restoreAtMark(mark);
|
||||
return null;
|
||||
}
|
||||
if (!this.accept(TokenType.ParenthesisL)) {
|
||||
return this.finish(node, ParseError.LeftParenthesisExpected);
|
||||
}
|
||||
if (!node.setSelector(this._parseSimpleSelector())) {
|
||||
return this.finish(node, ParseError.SelectorExpected);
|
||||
}
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected);
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMixinReference(): nodes.Node {
|
||||
if (!this.peek(TokenType.Delim, '.')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.MixinReference>this.create(nodes.MixinReference);
|
||||
|
||||
let identifier = <nodes.Identifier>this.create(nodes.Identifier);
|
||||
this.consumeToken(); // dot, part of the identifier
|
||||
if (this.hasWhitespace() || !this.accept(TokenType.Ident)) {
|
||||
return this.finish(node, ParseError.IdentifierExpected);
|
||||
}
|
||||
node.setIdentifier(this.finish(identifier));
|
||||
|
||||
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
|
||||
if (node.getArguments().addChild(this._parseFunctionArgument())) {
|
||||
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
|
||||
if (!node.getArguments().addChild(this._parseExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected);
|
||||
}
|
||||
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
|
||||
} else {
|
||||
identifier.referenceTypes = [nodes.ReferenceType.Mixin, nodes.ReferenceType.Rule];
|
||||
}
|
||||
|
||||
node.addChild(this._parsePrio());
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMixinParameter(): nodes.Node {
|
||||
|
||||
let node = <nodes.FunctionParameter>this.create(nodes.FunctionParameter);
|
||||
|
||||
// special rest variable: @rest...
|
||||
if (this.peek(TokenType.AtKeyword, '@rest')) {
|
||||
let restNode = this.create(nodes.Node);
|
||||
this.consumeToken();
|
||||
if (!this.accept(lessScanner.Ellipsis)) {
|
||||
return this.finish(node, ParseError.DotExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
|
||||
}
|
||||
node.setIdentifier(this.finish(restNode));
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
// special let args: ...
|
||||
if (this.peek(lessScanner.Ellipsis)) {
|
||||
let varargsNode = this.create(nodes.Node);
|
||||
this.consumeToken();
|
||||
node.setIdentifier(this.finish(varargsNode));
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
// default variable declaration: @param: 12 or @name
|
||||
if (node.setIdentifier(this._parseVariable())) {
|
||||
this.accept(TokenType.Colon);
|
||||
}
|
||||
node.setDefaultValue(this._parseExpr(true));
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseGuard(): nodes.LessGuard {
|
||||
|
||||
let node = <nodes.LessGuard>this.create(nodes.LessGuard);
|
||||
if (!this.accept(TokenType.Ident, 'when')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
node.isNegated = this.accept(TokenType.Ident, 'not');
|
||||
|
||||
if (!node.getConditions().addChild(this._parseGuardCondition())) {
|
||||
return <nodes.LessGuard>this.finish(node, ParseError.ConditionExpected);
|
||||
}
|
||||
while (this.accept(TokenType.Ident, 'and') || this.accept(TokenType.Comma, ',')) {
|
||||
if (!node.getConditions().addChild(this._parseGuardCondition())) {
|
||||
return <nodes.LessGuard>this.finish(node, ParseError.ConditionExpected);
|
||||
}
|
||||
}
|
||||
|
||||
return <nodes.LessGuard>this.finish(node);
|
||||
}
|
||||
|
||||
public _parseGuardCondition(): nodes.Node {
|
||||
let node = this.create(nodes.GuardCondition);
|
||||
if (!this.accept(TokenType.ParenthesisL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!node.addChild(this._parseExpr())) {
|
||||
// empty (?)
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected);
|
||||
}
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseFunctionIdentifier(): nodes.Identifier {
|
||||
if (this.peek(TokenType.Delim, '%')) {
|
||||
let node = <nodes.Identifier>this.create(nodes.Identifier);
|
||||
node.referenceTypes = [nodes.ReferenceType.Function];
|
||||
this.consumeToken();
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
return super._parseFunctionIdentifier();
|
||||
}
|
||||
}
|
73
extensions/css/server/src/parser/lessScanner.ts
Normal file
73
extensions/css/server/src/parser/lessScanner.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as scanner from './cssScanner';
|
||||
|
||||
const _FSL = '/'.charCodeAt(0);
|
||||
const _NWL = '\n'.charCodeAt(0);
|
||||
const _CAR = '\r'.charCodeAt(0);
|
||||
const _LFD = '\f'.charCodeAt(0);
|
||||
const _TIC = '`'.charCodeAt(0);
|
||||
const _DOT = '.'.charCodeAt(0);
|
||||
|
||||
let customTokenValue = scanner.TokenType.CustomToken;
|
||||
export const Ellipsis: scanner.TokenType = customTokenValue++;
|
||||
|
||||
export class LESSScanner extends scanner.Scanner {
|
||||
|
||||
public scan(): scanner.IToken {
|
||||
|
||||
let triviaToken = this.trivia();
|
||||
if (triviaToken !== null) {
|
||||
return triviaToken;
|
||||
}
|
||||
|
||||
let offset = this.stream.pos();
|
||||
|
||||
// LESS: escaped JavaScript code `let a = "dddd"`
|
||||
let tokenType = this.escapedJavaScript();
|
||||
if (tokenType !== null) {
|
||||
return this.finishToken(offset, tokenType);
|
||||
}
|
||||
|
||||
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
|
||||
return this.finishToken(offset, Ellipsis);
|
||||
}
|
||||
|
||||
return super.scan();
|
||||
}
|
||||
|
||||
protected comment(): boolean {
|
||||
if (super.comment()) {
|
||||
return true;
|
||||
}
|
||||
if (this.stream.advanceIfChars([_FSL, _FSL])) {
|
||||
this.stream.advanceWhileChar((ch: number) => {
|
||||
switch (ch) {
|
||||
case _NWL:
|
||||
case _CAR:
|
||||
case _LFD:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private escapedJavaScript(): scanner.TokenType {
|
||||
let ch = this.stream.peekChar();
|
||||
if (ch === _TIC) {
|
||||
this.stream.advance(1);
|
||||
this.stream.advanceWhileChar((ch) => { return ch !== _TIC; });
|
||||
return this.stream.advanceIfChar(_TIC) ? scanner.TokenType.EscapedJavaScript : scanner.TokenType.BadEscapedJavaScript;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
26
extensions/css/server/src/parser/scssErrors.ts
Normal file
26
extensions/css/server/src/parser/scssErrors.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from './cssNodes';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SCSSIssueType implements nodes.IRule {
|
||||
id: string;
|
||||
message: string;
|
||||
|
||||
public constructor(id: string, message: string) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
export var SCSSParseError = {
|
||||
FromExpected: new SCSSIssueType('scss-fromexpected', localize('expected.from', "'from' expected")),
|
||||
ThroughOrToExpected: new SCSSIssueType('scss-throughexpected', localize('expected.through', "'through' or 'to' expected")),
|
||||
InExpected: new SCSSIssueType('scss-fromexpected', localize('expected.in', "'in' expected")),
|
||||
};
|
535
extensions/css/server/src/parser/scssParser.ts
Normal file
535
extensions/css/server/src/parser/scssParser.ts
Normal file
|
@ -0,0 +1,535 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as scssScanner from './scssScanner';
|
||||
import {TokenType} from './cssScanner';
|
||||
import * as cssParser from './cssParser';
|
||||
import * as nodes from './cssNodes';
|
||||
|
||||
import {SCSSParseError} from './scssErrors';
|
||||
import {ParseError} from './cssErrors';
|
||||
|
||||
/// <summary>
|
||||
/// A parser for scss
|
||||
/// http://sass-lang.com/documentation/file.SASS_REFERENCE.html
|
||||
/// </summary>
|
||||
export class SCSSParser extends cssParser.Parser {
|
||||
|
||||
public constructor() {
|
||||
super(new scssScanner.SCSSScanner());
|
||||
}
|
||||
|
||||
public _parseStylesheetStatement(): nodes.Node {
|
||||
return super._parseStylesheetStatement()
|
||||
|| this._parseVariableDeclaration()
|
||||
|| this._parseWarnAndDebug()
|
||||
|| this._parseControlStatement()
|
||||
|| this._parseMixinDeclaration()
|
||||
|| this._parseMixinContent()
|
||||
|| this._parseMixinReference() // @include
|
||||
|| this._parseFunctionDeclaration();
|
||||
}
|
||||
|
||||
public _parseImport(): nodes.Node {
|
||||
let node = <nodes.Import>this.create(nodes.Import);
|
||||
if (!this.accept(TokenType.AtKeyword, '@import')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.URI) && !this.accept(TokenType.String)) {
|
||||
return this.finish(node, ParseError.URIOrStringExpected);
|
||||
}
|
||||
while (this.accept(TokenType.Comma)) {
|
||||
if (!this.accept(TokenType.URI) && !this.accept(TokenType.String)) {
|
||||
return this.finish(node, ParseError.URIOrStringExpected);
|
||||
}
|
||||
}
|
||||
|
||||
node.setMedialist(this._parseMediaList());
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
// scss variables: $font-size: 12px;
|
||||
public _parseVariableDeclaration(panic: TokenType[] = []): nodes.VariableDeclaration {
|
||||
let node = <nodes.VariableDeclaration>this.create(nodes.VariableDeclaration);
|
||||
|
||||
if (!node.setVariable(this._parseVariable())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.Colon, ':')) {
|
||||
return this.finish(node, ParseError.ColonExpected);
|
||||
}
|
||||
node.colonPosition = this.prevToken.offset;
|
||||
|
||||
if (!node.setValue(this._parseExpr())) {
|
||||
return this.finish(node, ParseError.VariableValueExpected, [], panic);
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.Exclamation)) {
|
||||
if (!this.accept(TokenType.Ident, 'default', true)) {
|
||||
return this.finish(node, ParseError.UnknownKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.peek(TokenType.SemiColon)) {
|
||||
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
||||
}
|
||||
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMediaFeatureName(): nodes.Node {
|
||||
return this._parseFunction() || this._parseIdent() || this._parseVariable(); // first function, the indent
|
||||
}
|
||||
|
||||
public _parseKeyframeSelector(): nodes.Node {
|
||||
return super._parseKeyframeSelector() || this._parseMixinContent();
|
||||
}
|
||||
|
||||
public _parseVariable(): nodes.Variable {
|
||||
let node = <nodes.Variable>this.create(nodes.Variable);
|
||||
if (!this.accept(scssScanner.VariableName)) {
|
||||
return null;
|
||||
}
|
||||
return <nodes.Variable>node;
|
||||
}
|
||||
|
||||
public _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier {
|
||||
let node = <nodes.Identifier>this.create(nodes.Identifier);
|
||||
node.referenceTypes = referenceTypes;
|
||||
let hasContent = false;
|
||||
while (this.accept(TokenType.Ident) || node.addChild(this._parseInterpolation())) {
|
||||
hasContent = true;
|
||||
if (!this.hasWhitespace() && this.accept(TokenType.Delim, '-')) {
|
||||
// '-' is a valid char inside a ident (special treatment here to support #{foo}-#{bar})
|
||||
}
|
||||
if (this.hasWhitespace()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hasContent ? this.finish(node) : null;
|
||||
}
|
||||
|
||||
public _parseTerm(): nodes.Term {
|
||||
let term = super._parseTerm();
|
||||
if (term) { return term; }
|
||||
|
||||
term = <nodes.Term>this.create(nodes.Term);
|
||||
if (term.setExpression(this._parseVariable())) {
|
||||
return <nodes.Term>this.finish(term);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseInterpolation(): nodes.Node {
|
||||
let node = this.create(nodes.Interpolation);
|
||||
if (this.accept(scssScanner.InterpolationFunction)) {
|
||||
if (!node.addChild(this._parseBinaryExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected);
|
||||
}
|
||||
if (!this.accept(TokenType.CurlyR)) {
|
||||
return this.finish(node, ParseError.RightCurlyExpected);
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseOperator(): nodes.Node {
|
||||
if (this.peek(scssScanner.EqualsOperator) || this.peek(scssScanner.NotEqualsOperator)
|
||||
|| this.peek(scssScanner.GreaterEqualsOperator) || this.peek(scssScanner.SmallerEqualsOperator)
|
||||
|| this.peek(TokenType.Delim, '>') || this.peek(TokenType.Delim, '<')
|
||||
|| this.peek(TokenType.Ident, 'and') || this.peek(TokenType.Ident, 'or')
|
||||
|| this.peek(TokenType.Delim, '%')
|
||||
) {
|
||||
let node = this.createNode(nodes.NodeType.Operator);
|
||||
this.consumeToken();
|
||||
return this.finish(node);
|
||||
}
|
||||
return super._parseOperator();
|
||||
}
|
||||
|
||||
public _parseUnaryOperator(): nodes.Node {
|
||||
if (this.peek(TokenType.Ident, 'not')) {
|
||||
let node = this.create(nodes.Node);
|
||||
this.consumeToken();
|
||||
return this.finish(node);
|
||||
}
|
||||
return super._parseUnaryOperator();
|
||||
}
|
||||
|
||||
public _parseRuleSetDeclaration(): nodes.Node {
|
||||
if (this.peek(TokenType.AtKeyword)) {
|
||||
return this._parseKeyframe() // nested @keyframe
|
||||
|| this._parseImport() // nested @import
|
||||
|| this._parseMedia() // nested @media
|
||||
|| this._parseFontFace() // nested @font-face
|
||||
|| this._parseWarnAndDebug() // @warn and @debug statements
|
||||
|| this._parseControlStatement() // @if, @while, @for, @each
|
||||
|| this._parseFunctionDeclaration() // @function
|
||||
|| this._parseExtends() // @extends
|
||||
|| this._parseMixinReference() // @include
|
||||
|| this._parseMixinContent() // @content
|
||||
|| this._parseMixinDeclaration(); // nested @mixin
|
||||
}
|
||||
return this._parseVariableDeclaration() // variable declaration
|
||||
|| this._tryParseRuleset(true) // nested ruleset
|
||||
|| super._parseRuleSetDeclaration(); // try css ruleset declaration as last so in the error case, the ast will contain a declaration
|
||||
}
|
||||
|
||||
public _parseDeclaration(resyncStopTokens?: TokenType[]): nodes.Declaration {
|
||||
let node = <nodes.Declaration>this.create(nodes.Declaration);
|
||||
if (!node.setProperty(this._parseProperty())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.Colon, ':')) {
|
||||
return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], resyncStopTokens);
|
||||
}
|
||||
node.colonPosition = this.prevToken.offset;
|
||||
|
||||
let hasContent = false;
|
||||
if (node.setValue(this._parseExpr())) {
|
||||
hasContent = true;
|
||||
node.addChild(this._parsePrio());
|
||||
}
|
||||
if (this.peek(TokenType.CurlyL)) {
|
||||
node.setNestedProperties(this._parseNestedProperties());
|
||||
} else {
|
||||
if (!hasContent) {
|
||||
return this.finish(node, ParseError.PropertyValueExpected);
|
||||
}
|
||||
}
|
||||
if (this.peek(TokenType.SemiColon)) {
|
||||
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseNestedProperties(): nodes.NestedProperties {
|
||||
let node = <nodes.NestedProperties>this.create(nodes.NestedProperties);
|
||||
return this._parseBody(node, this._parseDeclaration.bind(this));
|
||||
}
|
||||
|
||||
public _parseExtends(): nodes.Node {
|
||||
let node = <nodes.ExtendsReference>this.create(nodes.ExtendsReference);
|
||||
if (this.accept(TokenType.AtKeyword, '@extend')) {
|
||||
if (!node.setSelector(this._parseSimpleSelector())) {
|
||||
return this.finish(node, ParseError.SelectorExpected);
|
||||
}
|
||||
if (this.accept(TokenType.Exclamation)) {
|
||||
if (!this.accept(TokenType.Ident, 'optional', true)) {
|
||||
return this.finish(node, ParseError.UnknownKeyword);
|
||||
}
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseSimpleSelectorBody(): nodes.Node {
|
||||
return this._parseSelectorCombinator() || this._parseSelectorPlaceholder() || super._parseSimpleSelectorBody();
|
||||
}
|
||||
|
||||
public _parseSelectorCombinator(): nodes.Node {
|
||||
let node = this.createNode(nodes.NodeType.SelectorCombinator);
|
||||
if (this.accept(TokenType.Delim, '&')) {
|
||||
while (!this.hasWhitespace() && (this.accept(TokenType.Delim, '-') || node.addChild(this._parseIdent()) || this.accept(TokenType.Delim, '&'))) {
|
||||
// support &-foo
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseSelectorPlaceholder(): nodes.Node {
|
||||
let node = this.createNode(nodes.NodeType.SelectorPlaceholder);
|
||||
if (this.accept(TokenType.Delim, '%')) {
|
||||
this._parseIdent();
|
||||
return this.finish(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public _parseWarnAndDebug(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@debug') && !this.peek(TokenType.AtKeyword, '@warn')) {
|
||||
return null;
|
||||
}
|
||||
let node = this.createNode(nodes.NodeType.Debug);
|
||||
this.consumeToken(); // @debug or @warn
|
||||
node.addChild(this._parseExpr()); // optional
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseControlStatement(parseStatement: () => nodes.Node = this._parseRuleSetDeclaration.bind(this)): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword)) {
|
||||
return null;
|
||||
}
|
||||
return this._parseIfStatement(parseStatement) || this._parseForStatement(parseStatement)
|
||||
|| this._parseEachStatement(parseStatement) || this._parseWhileStatement(parseStatement);
|
||||
}
|
||||
|
||||
public _parseIfStatement(parseStatement: () => nodes.Node): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@if')) {
|
||||
return null;
|
||||
}
|
||||
return this._internalParseIfStatement(parseStatement);
|
||||
}
|
||||
|
||||
private _internalParseIfStatement(parseStatement: () => nodes.Node): nodes.IfStatement {
|
||||
let node = <nodes.IfStatement>this.create(nodes.IfStatement);
|
||||
this.consumeToken(); // @if or if
|
||||
if (!node.setExpression(this._parseBinaryExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected);
|
||||
}
|
||||
this._parseBody(node, parseStatement);
|
||||
if (this.accept(TokenType.AtKeyword, '@else')) {
|
||||
if (this.peek(TokenType.Ident, 'if')) {
|
||||
node.setElseClause(this._internalParseIfStatement(parseStatement));
|
||||
} else if (this.peek(TokenType.CurlyL)) {
|
||||
let elseNode = <nodes.BodyDeclaration>this.create(nodes.ElseStatement);
|
||||
this._parseBody(elseNode, parseStatement);
|
||||
node.setElseClause(elseNode);
|
||||
}
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseForStatement(parseStatement: () => nodes.Node): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@for')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.ForStatement>this.create(nodes.ForStatement);
|
||||
this.consumeToken(); // @for
|
||||
if (!node.setVariable(this._parseVariable())) {
|
||||
return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!this.accept(TokenType.Ident, 'from')) {
|
||||
return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!node.addChild(this._parseBinaryExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!this.accept(TokenType.Ident, 'to') && !this.accept(TokenType.Ident, 'through')) {
|
||||
return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!node.addChild(this._parseBinaryExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
return this._parseBody(node, parseStatement);
|
||||
}
|
||||
|
||||
public _parseEachStatement(parseStatement: () => nodes.Node): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@each')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.EachStatement>this.create(nodes.EachStatement);
|
||||
this.consumeToken(); // @each
|
||||
if (!node.setVariable(this._parseVariable())) {
|
||||
return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!this.accept(TokenType.Ident, 'in')) {
|
||||
return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
if (!node.addChild(this._parseExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
return this._parseBody(node, parseStatement);
|
||||
}
|
||||
|
||||
public _parseWhileStatement(parseStatement: () => nodes.Node): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@while')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.WhileStatement>this.create(nodes.WhileStatement);
|
||||
this.consumeToken(); // @while
|
||||
if (!node.addChild(this._parseBinaryExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
return this._parseBody(node, parseStatement);
|
||||
}
|
||||
|
||||
public _parseFunctionBodyDeclaration(): nodes.Node {
|
||||
return this._parseVariableDeclaration() || this._parseReturnStatement()
|
||||
|| this._parseControlStatement(this._parseFunctionBodyDeclaration.bind(this));
|
||||
}
|
||||
|
||||
public _parseFunctionDeclaration(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@function')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.FunctionDeclaration>this.create(nodes.FunctionDeclaration);
|
||||
this.consumeToken(); // @function
|
||||
|
||||
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) {
|
||||
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisL)) {
|
||||
return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
|
||||
while (this.accept(TokenType.Comma)) {
|
||||
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
|
||||
return this.finish(node, ParseError.VariableNameExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this));
|
||||
}
|
||||
|
||||
public _parseReturnStatement(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@return')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = this.createNode(nodes.NodeType.ReturnStatement);
|
||||
this.consumeToken(); // @function
|
||||
|
||||
if (!node.addChild(this._parseExpr())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected);
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMixinDeclaration(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@mixin')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.MixinDeclaration>this.create(nodes.MixinDeclaration);
|
||||
this.consumeToken();
|
||||
|
||||
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) {
|
||||
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.ParenthesisL)) {
|
||||
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
|
||||
while (this.accept(TokenType.Comma)) {
|
||||
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
|
||||
return this.finish(node, ParseError.VariableNameExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
}
|
||||
|
||||
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
||||
}
|
||||
|
||||
public _parseParameterDeclaration(): nodes.Node {
|
||||
|
||||
let node = <nodes.FunctionParameter>this.create(nodes.FunctionParameter);
|
||||
|
||||
if (!node.setIdentifier(this._parseVariable())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.accept(scssScanner.Ellipsis)) {
|
||||
// ok
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.Colon)) {
|
||||
if (!node.setDefaultValue(this._parseExpr(true))) {
|
||||
return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
|
||||
}
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMixinContent(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@content')) {
|
||||
return null;
|
||||
}
|
||||
let node = this.createNode(nodes.NodeType.MixinContent);
|
||||
this.consumeToken();
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
|
||||
public _parseMixinReference(): nodes.Node {
|
||||
if (!this.peek(TokenType.AtKeyword, '@include')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = <nodes.MixinReference>this.create(nodes.MixinReference);
|
||||
this.consumeToken();
|
||||
|
||||
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) {
|
||||
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
|
||||
}
|
||||
|
||||
if (this.accept(TokenType.ParenthesisL)) {
|
||||
if (node.getArguments().addChild(this._parseFunctionArgument())) {
|
||||
while (this.accept(TokenType.Comma)) {
|
||||
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
|
||||
return this.finish(node, ParseError.ExpressionExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.accept(TokenType.ParenthesisR)) {
|
||||
return this.finish(node, ParseError.RightParenthesisExpected);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.peek(TokenType.CurlyL)) {
|
||||
let content = <nodes.BodyDeclaration>this.create(nodes.BodyDeclaration);
|
||||
this._parseBody(content, this._parseMixinReferenceBodyStatement.bind(this));
|
||||
node.setContent(content);
|
||||
}
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
public _parseMixinReferenceBodyStatement(): nodes.Node {
|
||||
return this._parseRuleSetDeclaration() || this._parseKeyframeSelector();
|
||||
}
|
||||
|
||||
public _parseFunctionArgument(): nodes.Node {
|
||||
// [variableName ':'] expression | variableName '...'
|
||||
let node = <nodes.FunctionArgument>this.create(nodes.FunctionArgument);
|
||||
|
||||
let pos = this.mark();
|
||||
let argument = this._parseVariable();
|
||||
if (argument) {
|
||||
if (!this.accept(TokenType.Colon)) {
|
||||
if (this.accept(scssScanner.Ellipsis)) { // optional
|
||||
node.setValue(argument);
|
||||
return this.finish(node);
|
||||
} else {
|
||||
this.restoreAtMark(pos);
|
||||
}
|
||||
} else {
|
||||
node.setIdentifier(argument);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.setValue(this._parseExpr(true))) {
|
||||
return this.finish(node);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
115
extensions/css/server/src/parser/scssScanner.ts
Normal file
115
extensions/css/server/src/parser/scssScanner.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {TokenType, Scanner, IToken} from './cssScanner';
|
||||
|
||||
const _FSL = '/'.charCodeAt(0);
|
||||
const _NWL = '\n'.charCodeAt(0);
|
||||
const _CAR = '\r'.charCodeAt(0);
|
||||
const _LFD = '\f'.charCodeAt(0);
|
||||
|
||||
const _DLR = '$'.charCodeAt(0);
|
||||
const _HSH = '#'.charCodeAt(0);
|
||||
const _CUL = '{'.charCodeAt(0);
|
||||
const _EQS = '='.charCodeAt(0);
|
||||
const _BNG = '!'.charCodeAt(0);
|
||||
const _LAN = '<'.charCodeAt(0);
|
||||
const _RAN = '>'.charCodeAt(0);
|
||||
const _DOT = '.'.charCodeAt(0);
|
||||
|
||||
let customTokenValue = TokenType.CustomToken;
|
||||
|
||||
export const VariableName = customTokenValue++;
|
||||
export const InterpolationFunction: TokenType = customTokenValue++;
|
||||
export const Default: TokenType = customTokenValue++;
|
||||
export const EqualsOperator: TokenType = customTokenValue++;
|
||||
export const NotEqualsOperator: TokenType = customTokenValue++;
|
||||
export const GreaterEqualsOperator: TokenType = customTokenValue++;
|
||||
export const SmallerEqualsOperator: TokenType = customTokenValue++;
|
||||
export const Ellipsis: TokenType = customTokenValue++;
|
||||
|
||||
export class SCSSScanner extends Scanner {
|
||||
|
||||
public scan(): IToken {
|
||||
|
||||
// processes all whitespaces and comments
|
||||
const triviaToken = this.trivia();
|
||||
if (triviaToken !== null) {
|
||||
return triviaToken;
|
||||
}
|
||||
|
||||
const offset = this.stream.pos();
|
||||
|
||||
// scss variable
|
||||
if (this.stream.advanceIfChar(_DLR)) {
|
||||
const content = ['$'];
|
||||
if (this.ident(content)) {
|
||||
return this.finishToken(offset, VariableName, content.join(''));
|
||||
} else {
|
||||
this.stream.goBackTo(offset);
|
||||
}
|
||||
}
|
||||
|
||||
// scss: interpolation function #{..})
|
||||
if (this.stream.advanceIfChars([_HSH, _CUL])) {
|
||||
return this.finishToken(offset, InterpolationFunction);
|
||||
}
|
||||
|
||||
// operator ==
|
||||
if (this.stream.advanceIfChars([_EQS, _EQS])) {
|
||||
return this.finishToken(offset, EqualsOperator);
|
||||
}
|
||||
|
||||
// operator !=
|
||||
if (this.stream.advanceIfChars([_BNG, _EQS])) {
|
||||
return this.finishToken(offset, NotEqualsOperator);
|
||||
}
|
||||
|
||||
// operators <, <=
|
||||
if (this.stream.advanceIfChar(_LAN)) {
|
||||
if (this.stream.advanceIfChar(_EQS)) {
|
||||
return this.finishToken(offset, SmallerEqualsOperator);
|
||||
}
|
||||
return this.finishToken(offset, TokenType.Delim);
|
||||
}
|
||||
|
||||
// ooperators >, >=
|
||||
if (this.stream.advanceIfChar(_RAN)) {
|
||||
if (this.stream.advanceIfChar(_EQS)) {
|
||||
return this.finishToken(offset, GreaterEqualsOperator);
|
||||
}
|
||||
return this.finishToken(offset, TokenType.Delim);
|
||||
}
|
||||
|
||||
// ellipis
|
||||
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
|
||||
return this.finishToken(offset, Ellipsis);
|
||||
}
|
||||
|
||||
return super.scan();
|
||||
}
|
||||
|
||||
protected comment(): boolean {
|
||||
if (super.comment()) {
|
||||
return true;
|
||||
}
|
||||
if (this.stream.advanceIfChars([_FSL, _FSL])) {
|
||||
this.stream.advanceWhileChar((ch: number) => {
|
||||
switch (ch) {
|
||||
case _NWL:
|
||||
case _CAR:
|
||||
case _LFD:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
86
extensions/css/server/src/services/cssCodeActions.ts
Normal file
86
extensions/css/server/src/services/cssCodeActions.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import * as languageFacts from './languageFacts';
|
||||
import {difference} from '../utils/strings';
|
||||
import {Rules} from '../services/lintRules';
|
||||
import {TextDocument, Range, CodeActionContext, Diagnostic, Command, TextEdit} from 'vscode-languageserver';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CSSCodeActions {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public doCodeActions(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: nodes.Stylesheet): Thenable<Command[]> {
|
||||
let result: Command[] = [];
|
||||
if (context.diagnostics) {
|
||||
for (let diagnostic of context.diagnostics) {
|
||||
this.appendFixesForMarker(document, stylesheet, diagnostic, result);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private getFixesForUnknownProperty(document: TextDocument, property: nodes.Property, marker: Diagnostic, result: Command[]): void {
|
||||
|
||||
interface RankedProperty {
|
||||
property: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
let propertyName = property.getName();
|
||||
let candidates: RankedProperty[] = [];
|
||||
for (let p in languageFacts.getProperties()) {
|
||||
let score = difference(propertyName, p);
|
||||
if (score >= propertyName.length / 2 /*score_lim*/) {
|
||||
candidates.push({ property: p, score });
|
||||
}
|
||||
}
|
||||
|
||||
// Sort in descending order.
|
||||
candidates.sort((a, b) => {
|
||||
return b.score - a.score;
|
||||
});
|
||||
|
||||
let maxActions = 3;
|
||||
for (let candidate of candidates) {
|
||||
let propertyName = candidate.property;
|
||||
let title = localize('css.codeaction.rename', "Rename to '{0}'", propertyName);
|
||||
let edit = TextEdit.replace(marker.range, propertyName);
|
||||
result.push(Command.create(title, '_css.applyCodeAction', document.uri, document.version, [edit]));
|
||||
if (--maxActions <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private appendFixesForMarker(document: TextDocument, stylesheet: nodes.Stylesheet, marker: Diagnostic, result: Command[]): void {
|
||||
|
||||
if (marker.code !== Rules.UnknownProperty.id) {
|
||||
return;
|
||||
}
|
||||
let offset = document.offsetAt(marker.range.start);
|
||||
let end = document.offsetAt(marker.range.end);
|
||||
let nodepath = nodes.getNodePath(stylesheet, offset);
|
||||
|
||||
for (let i = nodepath.length - 1; i >= 0; i--) {
|
||||
let node = nodepath[i];
|
||||
if (node instanceof nodes.Declaration) {
|
||||
let property = (<nodes.Declaration>node).getProperty();
|
||||
if (property && property.offset === offset && property.end === end) {
|
||||
this.getFixesForUnknownProperty(document, property, marker, result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
665
extensions/css/server/src/services/cssCompletion.ts
Normal file
665
extensions/css/server/src/services/cssCompletion.ts
Normal file
|
@ -0,0 +1,665 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import {Symbols} from '../parser/cssSymbolScope';
|
||||
import * as languageFacts from './languageFacts';
|
||||
import * as strings from '../utils/strings';
|
||||
import {TextDocument, Position, CompletionList, CompletionItemKind} from 'vscode-languageserver';
|
||||
|
||||
export class CSSCompletion {
|
||||
|
||||
variablePrefix: string;
|
||||
position: Position;
|
||||
offset: number;
|
||||
currentWord: string;
|
||||
textDocument: TextDocument;
|
||||
styleSheet: nodes.Stylesheet;
|
||||
symbolContext: Symbols;
|
||||
|
||||
constructor(variablePrefix: string = null) {
|
||||
this.variablePrefix = variablePrefix;
|
||||
}
|
||||
|
||||
private getSymbolContext(): Symbols {
|
||||
if (!this.symbolContext) {
|
||||
this.symbolContext = new Symbols(this.styleSheet);
|
||||
}
|
||||
return this.symbolContext;
|
||||
}
|
||||
|
||||
|
||||
public doComplete(document: TextDocument, position: Position, styleSheet: nodes.Stylesheet): Thenable<CompletionList> {
|
||||
this.offset = document.offsetAt(position);
|
||||
this.position = position;
|
||||
this.currentWord = getCurrentWord(document, this.offset);
|
||||
this.textDocument = document;
|
||||
this.styleSheet = styleSheet;
|
||||
|
||||
let result: CompletionList = { isIncomplete: false, items: [] };
|
||||
let nodepath = nodes.getNodePath(this.styleSheet, this.offset);
|
||||
|
||||
for (let i = nodepath.length - 1; i >= 0; i--) {
|
||||
let node = nodepath[i];
|
||||
if (node instanceof nodes.Property) {
|
||||
this.getCompletionsForDeclarationProperty(result);
|
||||
} else if (node instanceof nodes.Expression) {
|
||||
this.getCompletionsForExpression(<nodes.Expression>node, result);
|
||||
} else if (node instanceof nodes.SimpleSelector) {
|
||||
let parentRuleSet = <nodes.RuleSet>node.findParent(nodes.NodeType.Ruleset);
|
||||
this.getCompletionsForSelector(parentRuleSet, result);
|
||||
} else if (node instanceof nodes.Declarations) {
|
||||
this.getCompletionsForDeclarations(<nodes.Declarations>node, result);
|
||||
} else if (node instanceof nodes.VariableDeclaration) {
|
||||
this.getCompletionsForVariableDeclaration(<nodes.VariableDeclaration>node, result);
|
||||
} else if (node instanceof nodes.RuleSet) {
|
||||
this.getCompletionsForRuleSet(<nodes.RuleSet>node, result);
|
||||
} else if (node instanceof nodes.Interpolation) {
|
||||
this.getCompletionsForInterpolation(<nodes.Interpolation>node, result);
|
||||
} else if (node instanceof nodes.FunctionArgument) {
|
||||
this.getCompletionsForFunctionArgument(<nodes.FunctionArgument>node, <nodes.Function>node.getParent(), result);
|
||||
} else if (node instanceof nodes.FunctionDeclaration) {
|
||||
this.getCompletionsForFunctionDeclaration(<nodes.FunctionDeclaration>node, result);
|
||||
} else if (node instanceof nodes.Function) {
|
||||
this.getCompletionsForFunctionArgument(null, <nodes.Function>node, result);
|
||||
}
|
||||
if (result.items.length > 0) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
this.getCompletionsForStylesheet(result);
|
||||
if (result.items.length > 0) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
if (this.variablePrefix && this.currentWord.indexOf(this.variablePrefix) === 0) {
|
||||
this.getVariableProposals(result);
|
||||
if (result.items.length > 0) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
// no match, don't show text matches
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
public getCompletionsForDeclarationProperty(result: CompletionList): CompletionList {
|
||||
return this.getPropertyProposals(result);
|
||||
}
|
||||
|
||||
private getPropertyProposals(result: CompletionList): CompletionList {
|
||||
let properties = languageFacts.getProperties();
|
||||
|
||||
for (let key in properties) {
|
||||
if (properties.hasOwnProperty(key)) {
|
||||
let entry = properties[key];
|
||||
if (entry.browsers.onCodeComplete) {
|
||||
result.items.push({
|
||||
label: entry.name,
|
||||
documentation: languageFacts.getEntryDescription(entry),
|
||||
insertText: entry.name + ': ',
|
||||
kind: CompletionItemKind.Property
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForDeclarationValue(node: nodes.Declaration, result: CompletionList): CompletionList {
|
||||
let propertyName = node.getFullPropertyName();
|
||||
let entry = languageFacts.getProperties()[propertyName];
|
||||
|
||||
if (entry) {
|
||||
this.getColorProposals(entry, result);
|
||||
this.getPositionProposals(entry, result);
|
||||
this.getRepeatStyleProposals(entry, result);
|
||||
this.getLineProposals(entry, result);
|
||||
this.getBoxProposals(entry, result);
|
||||
this.getImageProposals(entry, result);
|
||||
this.getTimingFunctionProposals(entry, result);
|
||||
this.getBasicShapeProposals(entry, result);
|
||||
this.getValueEnumProposals(entry, result);
|
||||
this.getCSSWideKeywordProposals(entry, result);
|
||||
this.getUnitProposals(entry, result);
|
||||
} else {
|
||||
let existingValues = new Set();
|
||||
this.styleSheet.accept(new ValuesCollector(propertyName, existingValues));
|
||||
existingValues.getEntries().forEach((existingValue) => {
|
||||
result.items.push({
|
||||
label: existingValue,
|
||||
insertText: existingValue,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
});
|
||||
}
|
||||
this.getVariableProposals(result);
|
||||
this.getTermProposals(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public getValueEnumProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.values) {
|
||||
entry.values.forEach((value) => {
|
||||
if (languageFacts.isCommonValue(value)) { // only show if supported by more than one browser
|
||||
result.items.push({
|
||||
label: value.name,
|
||||
documentation: languageFacts.getEntryDescription(value),
|
||||
insertText: value.name,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCSSWideKeywordProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
for (let keywords in languageFacts.cssWideKeywords) {
|
||||
result.items.push({
|
||||
label: keywords,
|
||||
documentation: languageFacts.cssWideKeywords[keywords],
|
||||
insertText: keywords,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForInterpolation(node: nodes.Interpolation, result: CompletionList): CompletionList {
|
||||
if (this.offset >= node.offset + 2) {
|
||||
this.getVariableProposals(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getVariableProposals(result: CompletionList): CompletionList {
|
||||
let symbols = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Variable);
|
||||
symbols.forEach((symbol) => {
|
||||
result.items.push({
|
||||
label: symbol.name,
|
||||
insertText: strings.startsWith(symbol.name, '--') ? `let(${symbol.name})` : symbol.name,
|
||||
kind: CompletionItemKind.Variable
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public getVariableProposalsForCSSVarFunction(result: CompletionList): CompletionList {
|
||||
let symbols = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Variable);
|
||||
symbols = symbols.filter((symbol): boolean => {
|
||||
return strings.startsWith(symbol.name, '--');
|
||||
});
|
||||
symbols.forEach((symbol) => {
|
||||
result.items.push({
|
||||
label: symbol.name,
|
||||
insertText: symbol.name,
|
||||
kind: CompletionItemKind.Variable
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public getUnitProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
let currentWord = '0';
|
||||
if (this.currentWord.length > 0) {
|
||||
let numMatch = this.currentWord.match(/-?\d[\.\d+]*/);
|
||||
if (numMatch) {
|
||||
currentWord = numMatch[0];
|
||||
}
|
||||
}
|
||||
entry.restrictions.forEach((restriction) => {
|
||||
let units = languageFacts.units[restriction];
|
||||
if (units) {
|
||||
units.forEach(function (unit: string) {
|
||||
result.items.push({
|
||||
label: currentWord + unit,
|
||||
insertText: currentWord + unit,
|
||||
kind: CompletionItemKind.Unit
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
result.isIncomplete = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getColorProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('color') !== -1) {
|
||||
for (let color in languageFacts.colors) {
|
||||
result.items.push({
|
||||
label: color,
|
||||
documentation: languageFacts.colors[color],
|
||||
insertText: color,
|
||||
kind: CompletionItemKind.Color
|
||||
});
|
||||
}
|
||||
for (let color in languageFacts.colorKeywords) {
|
||||
result.items.push({
|
||||
label: color,
|
||||
documentation: languageFacts.colorKeywords[color],
|
||||
insertText: color,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
let colorValues = new Set();
|
||||
this.styleSheet.accept(new ColorValueCollector(colorValues));
|
||||
colorValues.getEntries().forEach((color) => {
|
||||
result.items.push({
|
||||
label: color,
|
||||
insertText: color,
|
||||
kind: CompletionItemKind.Color
|
||||
});
|
||||
});
|
||||
languageFacts.colorFunctions.forEach((p) => {
|
||||
result.items.push({
|
||||
label: p.func.substr(0, p.func.indexOf('(')),
|
||||
detail: p.func,
|
||||
documentation: p.desc,
|
||||
insertText: p.func.replace(/\[?\$(\w+)\]?/g, '{{$1}}'),
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getPositionProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('position') !== -1) {
|
||||
for (let position in languageFacts.positionKeywords) {
|
||||
result.items.push({
|
||||
label: position,
|
||||
documentation: languageFacts.positionKeywords[position],
|
||||
insertText: position,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getRepeatStyleProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('repeat') !== -1) {
|
||||
for (let repeat in languageFacts.repeatStyleKeywords) {
|
||||
result.items.push({
|
||||
label: repeat,
|
||||
documentation: languageFacts.repeatStyleKeywords[repeat],
|
||||
insertText: repeat,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getLineProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('line-style') !== -1) {
|
||||
for (let lineStyle in languageFacts.lineStyleKeywords) {
|
||||
result.items.push({
|
||||
label: lineStyle,
|
||||
documentation: languageFacts.lineStyleKeywords[lineStyle],
|
||||
insertText: lineStyle,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
if (entry.restrictions.indexOf('line-width') !== -1) {
|
||||
languageFacts.lineWidthKeywords.forEach((lineWidth) => {
|
||||
result.items.push({
|
||||
label: lineWidth,
|
||||
insertText: lineWidth,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getBoxProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
let geometryBox = entry.restrictions.indexOf('geometry-box');
|
||||
if (geometryBox !== -1) {
|
||||
for (let box in languageFacts.geometryBoxKeywords) {
|
||||
result.items.push({
|
||||
label: box,
|
||||
documentation: languageFacts.geometryBoxKeywords[box],
|
||||
insertText: box,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
if (entry.restrictions.indexOf('box') !== -1 || geometryBox !== -1) {
|
||||
for (let box in languageFacts.boxKeywords) {
|
||||
result.items.push({
|
||||
label: box,
|
||||
documentation: languageFacts.boxKeywords[box],
|
||||
insertText: box,
|
||||
kind: CompletionItemKind.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getImageProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('image') !== -1) {
|
||||
for (let image in languageFacts.imageFunctions) {
|
||||
result.items.push({
|
||||
label: image,
|
||||
documentation: languageFacts.imageFunctions[image],
|
||||
insertText: image,
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getTimingFunctionProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('timing-function') !== -1) {
|
||||
for (let timing in languageFacts.transitionTimingFunctions) {
|
||||
result.items.push({
|
||||
label: timing,
|
||||
documentation: languageFacts.transitionTimingFunctions[timing],
|
||||
insertText: timing,
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getBasicShapeProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
if (entry.restrictions.indexOf('shape') !== -1) {
|
||||
for (let shape in languageFacts.basicShapeFunctions) {
|
||||
result.items.push({
|
||||
label: shape,
|
||||
documentation: languageFacts.basicShapeFunctions[shape],
|
||||
insertText: shape,
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForStylesheet(result: CompletionList): CompletionList {
|
||||
let node = this.styleSheet.findFirstChildBeforeOffset(this.offset);
|
||||
if (!node) {
|
||||
return this.getCompletionForTopLevel(result);
|
||||
}
|
||||
if (node instanceof nodes.RuleSet) {
|
||||
return this.getCompletionsForRuleSet(<nodes.RuleSet>node, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionForTopLevel(result: CompletionList): CompletionList {
|
||||
languageFacts.getAtDirectives().forEach(function (entry) {
|
||||
if (entry.browsers.count > 0) {
|
||||
result.items.push({
|
||||
label: entry.name,
|
||||
insertText: entry.name,
|
||||
documentation: languageFacts.getEntryDescription(entry),
|
||||
kind: CompletionItemKind.Keyword
|
||||
});
|
||||
}
|
||||
});
|
||||
this.getCompletionsForSelector(null, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForRuleSet(ruleSet: nodes.RuleSet, result: CompletionList): CompletionList {
|
||||
let declarations = ruleSet.getDeclarations();
|
||||
|
||||
let isAfter = declarations && declarations.endsWith('}') && this.offset >= declarations.end;
|
||||
if (isAfter) {
|
||||
return this.getCompletionForTopLevel(result);
|
||||
}
|
||||
let isInSelectors = !declarations || this.offset <= declarations.offset;
|
||||
if (isInSelectors) {
|
||||
return this.getCompletionsForSelector(ruleSet, result);
|
||||
}
|
||||
ruleSet.findParent(nodes.NodeType.Ruleset);
|
||||
|
||||
return this.getCompletionsForDeclarations(ruleSet.getDeclarations(), result);
|
||||
}
|
||||
|
||||
public getCompletionsForSelector(ruleSet: nodes.RuleSet, result: CompletionList): CompletionList {
|
||||
languageFacts.getPseudoClasses().forEach((entry) => {
|
||||
if (entry.browsers.onCodeComplete) {
|
||||
result.items.push({
|
||||
label: entry.name,
|
||||
insertText: entry.name,
|
||||
documentation: languageFacts.getEntryDescription(entry),
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
});
|
||||
languageFacts.getPseudoElements().forEach((entry) => {
|
||||
if (entry.browsers.onCodeComplete) {
|
||||
result.items.push({
|
||||
label: entry.name,
|
||||
insertText: entry.name,
|
||||
documentation: languageFacts.getEntryDescription(entry),
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
});
|
||||
languageFacts.html5Tags.forEach((entry) => {
|
||||
result.items.push({
|
||||
label: entry,
|
||||
insertText: entry,
|
||||
kind: CompletionItemKind.Keyword
|
||||
});
|
||||
});
|
||||
languageFacts.svgElements.forEach((entry) => {
|
||||
result.items.push({
|
||||
label: entry,
|
||||
insertText: entry,
|
||||
kind: CompletionItemKind.Keyword
|
||||
});
|
||||
});
|
||||
|
||||
let visited: { [name: string]: boolean } = {};
|
||||
visited[this.currentWord] = true;
|
||||
let textProvider = this.styleSheet.getTextProvider();
|
||||
this.styleSheet.accept(n => {
|
||||
if (n.type === nodes.NodeType.SimpleSelector && n.length > 0) {
|
||||
let selector = textProvider(n.offset, n.length);
|
||||
if (selector.charAt(0) === '.' && !visited[selector]) {
|
||||
visited[selector] = true;
|
||||
result.items.push({
|
||||
label: selector,
|
||||
insertText: selector,
|
||||
kind: CompletionItemKind.Keyword
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (ruleSet && ruleSet.isNested()) {
|
||||
let selector = ruleSet.getSelectors().findFirstChildBeforeOffset(this.offset);
|
||||
if (selector && ruleSet.getSelectors().getChildren().indexOf(selector) === 0) {
|
||||
this.getPropertyProposals(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForDeclarations(declarations: nodes.Declarations, result: CompletionList): CompletionList {
|
||||
if (!declarations) { // incomplete nodes
|
||||
return result;
|
||||
}
|
||||
|
||||
let node = declarations.findFirstChildBeforeOffset(this.offset);
|
||||
if (!node) {
|
||||
return this.getCompletionsForDeclarationProperty(result);
|
||||
}
|
||||
|
||||
if (node instanceof nodes.AbstractDeclaration) {
|
||||
let declaration = <nodes.AbstractDeclaration>node;
|
||||
if ((!isDefined(declaration.colonPosition) || this.offset <= declaration.colonPosition) || (isDefined(declaration.semicolonPosition) && declaration.semicolonPosition < this.offset)) {
|
||||
if (this.offset === declaration.semicolonPosition + 1) {
|
||||
return result; // don't show new properties right after semicolon (see Bug 15421:[intellisense] [css] Be less aggressive when manually typing CSS)
|
||||
}
|
||||
|
||||
// complete property
|
||||
return this.getCompletionsForDeclarationProperty(result);
|
||||
}
|
||||
|
||||
if (declaration instanceof nodes.Declaration) {
|
||||
// complete value
|
||||
return this.getCompletionsForDeclarationValue(declaration, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForVariableDeclaration(declaration: nodes.VariableDeclaration, result: CompletionList): CompletionList {
|
||||
if (this.offset > declaration.colonPosition) {
|
||||
this.getVariableProposals(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForExpression(expression: nodes.Expression, result: CompletionList): CompletionList {
|
||||
if (expression.getParent() instanceof nodes.FunctionArgument) {
|
||||
this.getCompletionsForFunctionArgument(<nodes.FunctionArgument>expression.getParent(), <nodes.Function>expression.getParent().getParent(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let declaration = <nodes.Declaration>expression.findParent(nodes.NodeType.Declaration);
|
||||
if (!declaration) {
|
||||
this.getTermProposals(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let node = expression.findChildAtOffset(this.offset, true);
|
||||
if (!node) {
|
||||
return this.getCompletionsForDeclarationValue(declaration, result);
|
||||
}
|
||||
if (node instanceof nodes.NumericValue || node instanceof nodes.Identifier) {
|
||||
return this.getCompletionsForDeclarationValue(declaration, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForFunctionArgument(arg: nodes.FunctionArgument, func: nodes.Function, result: CompletionList): CompletionList {
|
||||
if (func.getIdentifier().getText() === 'let') {
|
||||
if (!func.getArguments().hasChildren() || func.getArguments().getChild(0) === arg) {
|
||||
this.getVariableProposalsForCSSVarFunction(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForFunctionDeclaration(decl: nodes.FunctionDeclaration, result: CompletionList): CompletionList {
|
||||
let declarations = decl.getDeclarations();
|
||||
if (declarations && this.offset > declarations.offset && this.offset < declarations.end) {
|
||||
this.getTermProposals(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getTermProposals(result: CompletionList): CompletionList {
|
||||
let allFunctions = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Function);
|
||||
allFunctions.forEach((functionSymbol) => {
|
||||
if (functionSymbol.node instanceof nodes.FunctionDeclaration) {
|
||||
let functionDecl = <nodes.FunctionDeclaration>functionSymbol.node;
|
||||
let params = functionDecl.getParameters().getChildren().map((c) => {
|
||||
return (c instanceof nodes.FunctionParameter) ? (<nodes.FunctionParameter>c).getName() : c.getText();
|
||||
});
|
||||
result.items.push({
|
||||
label: functionSymbol.name,
|
||||
detail: functionSymbol.name + '(' + params.join(', ') + ')',
|
||||
insertText: functionSymbol.name + '(' + params.map((p) => '{{' + p + '}}').join(', ') + ')',
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Set {
|
||||
private entries: { [key: string]: boolean } = {};
|
||||
public add(entry: string): void {
|
||||
this.entries[entry] = true;
|
||||
}
|
||||
public getEntries(): string[] {
|
||||
return Object.keys(this.entries);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class InternalValueCollector implements nodes.IVisitor {
|
||||
|
||||
constructor(public entries: Set) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
if (node instanceof nodes.Identifier || node instanceof nodes.NumericValue || node instanceof nodes.HexColorValue) {
|
||||
this.entries.add(node.getText());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ValuesCollector implements nodes.IVisitor {
|
||||
|
||||
|
||||
constructor(public propertyName: string, public entries: Set) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
private matchesProperty(decl: nodes.Declaration): boolean {
|
||||
let propertyName = decl.getFullPropertyName();
|
||||
return this.propertyName === propertyName;
|
||||
}
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
if (node instanceof nodes.Declaration) {
|
||||
if (this.matchesProperty(<nodes.Declaration>node)) {
|
||||
let value = (<nodes.Declaration>node).getValue();
|
||||
if (value) {
|
||||
value.accept(new InternalValueCollector(this.entries));
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ColorValueCollector implements nodes.IVisitor {
|
||||
|
||||
constructor(public entries: Set) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
if (node instanceof nodes.HexColorValue || (node instanceof nodes.Function && languageFacts.isColorConstructor(<nodes.Function>node))) {
|
||||
this.entries.add(node.getText());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isDefined(obj: any): boolean {
|
||||
return typeof obj !== 'undefined';
|
||||
}
|
||||
|
||||
function getCurrentWord(document: TextDocument, offset: number) {
|
||||
let i = offset - 1;
|
||||
let text = document.getText();
|
||||
while (i >= 0 && ' \t\n\r":{[,'.indexOf(text.charAt(i)) === -1) {
|
||||
i--;
|
||||
}
|
||||
return text.substring(i + 1, offset);
|
||||
}
|
55
extensions/css/server/src/services/cssHover.ts
Normal file
55
extensions/css/server/src/services/cssHover.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import * as languageFacts from './languageFacts';
|
||||
import {TextDocument, Range, Position, Hover} from 'vscode-languageserver';
|
||||
import {selectorToMarkedString, simpleSelectorToMarkedString} from './selectorPrinting';
|
||||
|
||||
export class CSSHover {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public doHover(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Thenable<Hover> {
|
||||
|
||||
function getRange(node: nodes.Node) {
|
||||
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
|
||||
}
|
||||
|
||||
let offset = document.offsetAt(position);
|
||||
let nodepath = nodes.getNodePath(stylesheet, offset);
|
||||
|
||||
for (let i = 0; i < nodepath.length; i++) {
|
||||
let node = nodepath[i];
|
||||
if (node instanceof nodes.Selector) {
|
||||
return Promise.resolve({
|
||||
contents: selectorToMarkedString(<nodes.Selector>node),
|
||||
range: getRange(node)
|
||||
});
|
||||
}
|
||||
if (node instanceof nodes.SimpleSelector) {
|
||||
return Promise.resolve({
|
||||
contents: simpleSelectorToMarkedString(<nodes.SimpleSelector>node),
|
||||
range: getRange(node)
|
||||
});
|
||||
}
|
||||
if (node instanceof nodes.Declaration) {
|
||||
let propertyName = node.getFullPropertyName();
|
||||
let entry = languageFacts.getProperties()[propertyName];
|
||||
if (entry && entry.description) {
|
||||
return Promise.resolve({
|
||||
contents: entry.description,
|
||||
range: getRange(node)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
186
extensions/css/server/src/services/cssNavigation.ts
Normal file
186
extensions/css/server/src/services/cssNavigation.ts
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import {TextDocument, Range, Position, Location, DocumentHighlightKind, DocumentHighlight,
|
||||
SymbolInformation, SymbolKind, WorkspaceEdit, TextEdit} from 'vscode-languageserver';
|
||||
import {Symbols} from '../parser/cssSymbolScope';
|
||||
import {isColorValue} from '../services/languageFacts';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CSSNavigation {
|
||||
|
||||
public findDefinition(document: TextDocument, position: Position, stylesheet: nodes.Node): Thenable<Location> {
|
||||
|
||||
let symbols = new Symbols(stylesheet);
|
||||
let offset = document.offsetAt(position);
|
||||
let node = nodes.getNodeAtOffset(stylesheet, offset);
|
||||
|
||||
if (!node) {
|
||||
//workaround for https://github.com/Microsoft/vscode-languageserver-node/issues/45
|
||||
return Promise.resolve({
|
||||
uri: document.uri,
|
||||
range: Range.create(position, position)
|
||||
});
|
||||
}
|
||||
|
||||
let symbol = symbols.findSymbolFromNode(node);
|
||||
if (!symbol) {
|
||||
//workaround for https://github.com/Microsoft/vscode-languageserver-node/issues/45
|
||||
return Promise.resolve({
|
||||
uri: document.uri,
|
||||
range: Range.create(position, position)
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
uri: document.uri,
|
||||
range: getRange(symbol.node, document)
|
||||
});
|
||||
}
|
||||
|
||||
public findReferences(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Thenable<Location[]> {
|
||||
return this.findDocumentHighlights(document, position, stylesheet).then(highlights => highlights.map(h => {
|
||||
return {
|
||||
uri: document.uri,
|
||||
range: h.range
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
public findDocumentHighlights(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Thenable<DocumentHighlight[]> {
|
||||
let result: DocumentHighlight[] = [];
|
||||
|
||||
let offset = document.offsetAt(position);
|
||||
let node = nodes.getNodeAtOffset(stylesheet, offset);
|
||||
if (!node || node.type === nodes.NodeType.Stylesheet || node.type === nodes.NodeType.Declarations) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let symbols = new Symbols(stylesheet);
|
||||
let symbol = symbols.findSymbolFromNode(node);
|
||||
let name = node.getText();
|
||||
|
||||
stylesheet.accept(candidate => {
|
||||
if (symbol) {
|
||||
if (symbols.matchesSymbol(candidate, symbol)) {
|
||||
result.push({
|
||||
kind: getHighlightKind(candidate),
|
||||
range: getRange(candidate, document)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else if (node.type === candidate.type && node.length === candidate.length && name === candidate.getText()) {
|
||||
// Same node type and data
|
||||
result.push({
|
||||
kind: getHighlightKind(candidate),
|
||||
range: getRange(candidate, document)
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
public findDocumentSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): Thenable<SymbolInformation[]> {
|
||||
|
||||
let result: SymbolInformation[] = [];
|
||||
|
||||
stylesheet.accept((node) => {
|
||||
|
||||
let entry: SymbolInformation = {
|
||||
name: null,
|
||||
kind: SymbolKind.Class, // TODO@Martin: find a good SymbolKind
|
||||
location: null
|
||||
};
|
||||
|
||||
if (node instanceof nodes.Selector) {
|
||||
entry.name = node.getText();
|
||||
} else if (node instanceof nodes.VariableDeclaration) {
|
||||
entry.name = (<nodes.VariableDeclaration>node).getName();
|
||||
entry.kind = SymbolKind.Variable;
|
||||
} else if (node instanceof nodes.MixinDeclaration) {
|
||||
entry.name = (<nodes.MixinDeclaration>node).getName();
|
||||
entry.kind = SymbolKind.Method;
|
||||
} else if (node instanceof nodes.FunctionDeclaration) {
|
||||
entry.name = (<nodes.FunctionDeclaration>node).getName();
|
||||
entry.kind = SymbolKind.Function;
|
||||
} else if (node instanceof nodes.Keyframe) {
|
||||
entry.name = localize('literal.keyframes', "@keyframes {0}", (<nodes.Keyframe>node).getName());
|
||||
} else if (node instanceof nodes.FontFace) {
|
||||
entry.name = localize('literal.fontface', "@font-face");
|
||||
}
|
||||
|
||||
if (entry.name) {
|
||||
entry.location = Location.create(document.uri, getRange(node, document));
|
||||
result.push(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
public findColorSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): Thenable<Range[]> {
|
||||
let result: Range[] = [];
|
||||
stylesheet.accept((node) => {
|
||||
if (isColorValue(node)) {
|
||||
result.push(getRange(node, document));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
public doRename(document: TextDocument, position: Position, newName: string, stylesheet: nodes.Stylesheet): Thenable<WorkspaceEdit> {
|
||||
return this.findDocumentHighlights(document, position, stylesheet).then(highlights => {
|
||||
let edits = highlights.map(h => TextEdit.replace(h.range, newName));
|
||||
return {
|
||||
changes: {
|
||||
[document.uri]: edits
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getRange(node: nodes.Node, document: TextDocument) : Range {
|
||||
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
|
||||
}
|
||||
|
||||
function getHighlightKind(node: nodes.Node): DocumentHighlightKind {
|
||||
|
||||
if (node.type === nodes.NodeType.Selector) {
|
||||
return DocumentHighlightKind.Write;
|
||||
}
|
||||
|
||||
if (node instanceof nodes.Identifier) {
|
||||
if (node.parent && node.parent instanceof nodes.Property) {
|
||||
if (Symbols.isCssVariable(node)) {
|
||||
return DocumentHighlightKind.Write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.parent) {
|
||||
switch (node.parent.type) {
|
||||
case nodes.NodeType.FunctionDeclaration:
|
||||
case nodes.NodeType.MixinDeclaration:
|
||||
case nodes.NodeType.Keyframe:
|
||||
case nodes.NodeType.VariableDeclaration:
|
||||
case nodes.NodeType.FunctionParameter:
|
||||
return DocumentHighlightKind.Write;
|
||||
}
|
||||
}
|
||||
|
||||
return DocumentHighlightKind.Read;
|
||||
}
|
||||
|
53
extensions/css/server/src/services/cssValidation.ts
Normal file
53
extensions/css/server/src/services/cssValidation.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import {TextDocument, Range, Diagnostic, DiagnosticSeverity} from 'vscode-languageserver';
|
||||
import {ILintConfigurationSettings, sanitize} from './lintRules';
|
||||
import {LintVisitor} from './lint';
|
||||
import {LanguageSettings} from '../cssLanguageService';
|
||||
|
||||
export class CSSValidation {
|
||||
|
||||
private lintSettings: ILintConfigurationSettings;
|
||||
private validationEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public configure(raw: LanguageSettings) {
|
||||
if (raw) {
|
||||
this.validationEnabled = raw.validate;
|
||||
if (raw.lint) {
|
||||
this.lintSettings = sanitize(raw.lint);
|
||||
} else {
|
||||
this.lintSettings = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public doValidation(document: TextDocument, stylesheet: nodes.Stylesheet): Thenable<Diagnostic[]> {
|
||||
if (!this.validationEnabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
let entries: nodes.IMarker[] = [];
|
||||
entries.push.apply(entries, nodes.ParseErrorCollector.entries(stylesheet));
|
||||
entries.push.apply(entries, LintVisitor.entries(stylesheet, this.lintSettings));
|
||||
|
||||
function toDiagnostic(marker: nodes.IMarker): Diagnostic {
|
||||
let range = Range.create(document.positionAt(marker.getOffset()), document.positionAt(marker.getOffset() + marker.getLength()));
|
||||
return <Diagnostic>{
|
||||
code: marker.getRule().id,
|
||||
message: marker.getMessage(),
|
||||
severity: marker.getLevel() === nodes.Level.Warning ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
|
||||
range: range
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.resolve(entries.filter(entry => entry.getLevel() !== nodes.Level.Ignore).map(toDiagnostic));
|
||||
}
|
||||
}
|
588
extensions/css/server/src/services/languageFacts.ts
Normal file
588
extensions/css/server/src/services/languageFacts.ts
Normal file
|
@ -0,0 +1,588 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import * as browsers from '../data/browsers';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export let colors : { [name:string]:string } = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
grey: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370d8',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#d87093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rebeccapurple: '#663399',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
|
||||
export let colorKeywords : { [name:string]:string } = {
|
||||
'currentColor': 'The value of the \'color\' property. The computed value of the \'currentColor\' keyword is the computed value of the \'color\' property. If the \'currentColor\' keyword is set on the \'color\' property itself, it is treated as \'color:inherit\' at parse time.',
|
||||
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
|
||||
};
|
||||
|
||||
export let positionKeywords : { [name:string]:string } = {
|
||||
'bottom': 'Computes to ‘100%’ for the vertical position if one or two values are given, otherwise specifies the bottom edge as the origin for the next offset.',
|
||||
'center': 'Computes to ‘50%’ (‘left 50%’) for the horizontal position if the horizontal position is not otherwise specified, or ‘50%’ (‘top 50%’) for the vertical position if it is.',
|
||||
'left': 'Computes to ‘0%’ for the horizontal position if one or two values are given, otherwise specifies the left edge as the origin for the next offset.',
|
||||
'right': 'Computes to ‘100%’ for the horizontal position if one or two values are given, otherwise specifies the right edge as the origin for the next offset.',
|
||||
'top': 'Computes to ‘0%’ for the vertical position if one or two values are given, otherwise specifies the top edge as the origin for the next offset.'
|
||||
};
|
||||
|
||||
export let repeatStyleKeywords : { [name:string]:string } = {
|
||||
'no-repeat': 'Placed once and not repeated in this direction.',
|
||||
'repeat': 'Repeated in this direction as often as needed to cover the background painting area.',
|
||||
'repeat-x': 'Computes to ‘repeat no-repeat’.',
|
||||
'repeat-y': 'Computes to ‘no-repeat repeat’.',
|
||||
'round': 'Repeated as often as will fit within the background positioning area. If it doesn’t fit a whole number of times, it is rescaled so that it does.',
|
||||
'space': 'Repeated as often as will fit within the background positioning area without being clipped and then the images are spaced out to fill the area.'
|
||||
};
|
||||
|
||||
export let lineStyleKeywords : { [name:string]:string } = {
|
||||
'dashed': 'A series of square-ended dashes.',
|
||||
'dotted': 'A series of round dots.',
|
||||
'double': 'Two parallel solid lines with some space between them.',
|
||||
'groove': 'Looks as if it were carved in the canvas.',
|
||||
'hidden': 'Same as ‘none’, but has different behavior in the border conflict resolution rules for border-collapsed tables.',
|
||||
'inset': 'Looks as if the content on the inside of the border is sunken into the canvas.',
|
||||
'none': 'No border. Color and width are ignored.',
|
||||
'outset': 'Looks as if the content on the inside of the border is coming out of the canvas.',
|
||||
'ridge': 'Looks as if it were coming out of the canvas.',
|
||||
'solid': 'A single line segment.'
|
||||
};
|
||||
|
||||
export let lineWidthKeywords = ['medium', 'thick', 'thin'];
|
||||
|
||||
export let boxKeywords : { [name:string]:string } = {
|
||||
'border-box': 'The background is painted within (clipped to) the border box.',
|
||||
'content-box': 'The background is painted within (clipped to) the content box.',
|
||||
'padding-box': 'The background is painted within (clipped to) the padding box.'
|
||||
};
|
||||
|
||||
export let geometryBoxKeywords : { [name:string]:string } = {
|
||||
'margin-box': 'Uses the margin box as reference box.',
|
||||
'fill-box': 'Uses the object bounding box as reference box.',
|
||||
'stroke-box': 'Uses the stroke bounding box as reference box.',
|
||||
'view-box': 'Uses the nearest SVG viewport as reference box.'
|
||||
};
|
||||
|
||||
export let cssWideKeywords : { [name:string]:string } = {
|
||||
'initial': 'Represents the value specified as the property’s initial value.',
|
||||
'inherit': 'Represents the computed value of the property on the element’s parent.',
|
||||
'unset': 'Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.'
|
||||
};
|
||||
|
||||
export let colorFunctions = [
|
||||
{ func: 'rgb($red, $green, $blue)', desc: localize('css.builtin.rgb', 'Creates a Color from red, green, and blue values.') },
|
||||
{ func: 'rgba($red, $green, $blue, $alpha)', desc: localize('css.builtin.rgba', 'Creates a Color from red, green, blue, and alpha values.') },
|
||||
{ func: 'hsl($hue, $saturation, $lightness)', desc: localize('css.builtin.hsl', 'Creates a Color from hue, saturation, and lightness values.') },
|
||||
{ func: 'hsla($hue, $saturation, $lightness, $alpha)', desc: localize('css.builtin.hsla', 'Creates a Color from hue, saturation, lightness, and alpha values.') }
|
||||
];
|
||||
|
||||
export let imageFunctions : { [name:string]:string } = {
|
||||
'url()': 'Reference an image file by URL',
|
||||
'image()': 'Provide image fallbacks and annotations.',
|
||||
'-webkit-image-set()': 'Provide multiple resolutions. Remember to use unprefixed image-set() in addition.',
|
||||
'image-set()': 'Provide multiple resolutions of an image and let the UA decide which is most appropriate in a given situation.',
|
||||
'-moz-element()': 'Use an element in the document as an image. Remember to use unprefixed element() in addition.',
|
||||
'element()': 'Use an element in the document as an image.',
|
||||
'cross-fade()': 'Indicates the two images to be combined and how far along in the transition the combination is.',
|
||||
'-webkit-gradient()': 'Deprecated. Use modern linear-gradient() or radial-gradient() instead.',
|
||||
'-webkit-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
|
||||
'-moz-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
|
||||
'-o-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
|
||||
'linear-gradient()': 'A linear gradient is created by specifying a straight gradient line, and then several colors placed along that line.',
|
||||
'-webkit-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
|
||||
'-moz-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
|
||||
'-o-repeating-linear-gradient()': 'RepeatingLinear gradient. Remember to use unprefixed version in addition.',
|
||||
'repeating-linear-gradient()': 'Same as linear-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.',
|
||||
'-webkit-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
|
||||
'-moz-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
|
||||
'radial-gradient()': 'Colors emerge from a single point and smoothly spread outward in a circular or elliptical shape.',
|
||||
'-webkit-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
|
||||
'-moz-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
|
||||
'repeating-radial-gradient()': 'Same as radial-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.'
|
||||
};
|
||||
|
||||
export let transitionTimingFunctions : { [name:string]:string } = {
|
||||
'ease': 'Equivalent to cubic-bezier(0.25, 0.1, 0.25, 1.0).',
|
||||
'ease-in': 'Equivalent to cubic-bezier(0.42, 0, 1.0, 1.0).',
|
||||
'ease-in-out': 'Equivalent to cubic-bezier(0.42, 0, 0.58, 1.0).',
|
||||
'ease-out': 'Equivalent to cubic-bezier(0, 0, 0.58, 1.0).',
|
||||
'linear': 'Equivalent to cubic-bezier(0.0, 0.0, 1.0, 1.0).',
|
||||
'step-end': 'Equivalent to steps(1, end).',
|
||||
'step-start': 'Equivalent to steps(1, start).',
|
||||
'steps()': 'The first parameter specifies the number of intervals in the function. The second parameter, which is optional, is either the value “start” or “end”.',
|
||||
'cubic-bezier()': 'Specifies a cubic-bezier curve. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2).',
|
||||
'cubic-bezier(0.6, -0.28, 0.735, 0.045)': 'Ease-in Back. Overshoots.',
|
||||
'cubic-bezier(0.68, -0.55, 0.265, 1.55)': 'Ease-in-out Back. Overshoots.',
|
||||
'cubic-bezier(0.175, 0.885, 0.32, 1.275)': 'Ease-out Back. Overshoots.',
|
||||
'cubic-bezier(0.6, 0.04, 0.98, 0.335)': 'Ease-in Circular. Based on half circle.',
|
||||
'cubic-bezier(0.785, 0.135, 0.15, 0.86)': 'Ease-in-out Circular. Based on half circle.',
|
||||
'cubic-bezier(0.075, 0.82, 0.165, 1)': 'Ease-out Circular. Based on half circle.',
|
||||
'cubic-bezier(0.55, 0.055, 0.675, 0.19)': 'Ease-in Cubic. Based on power of three.',
|
||||
'cubic-bezier(0.645, 0.045, 0.355, 1)': 'Ease-in-out Cubic. Based on power of three.',
|
||||
'cubic-bezier(0.215, 0.610, 0.355, 1)': 'Ease-out Cubic. Based on power of three.',
|
||||
'cubic-bezier(0.95, 0.05, 0.795, 0.035)': 'Ease-in Exponential. Based on two to the power ten.',
|
||||
'cubic-bezier(1, 0, 0, 1)': 'Ease-in-out Exponential. Based on two to the power ten.',
|
||||
'cubic-bezier(0.19, 1, 0.22, 1)': 'Ease-out Exponential. Based on two to the power ten.',
|
||||
'cubic-bezier(0.47, 0, 0.745, 0.715)': 'Ease-in Sine.',
|
||||
'cubic-bezier(0.445, 0.05, 0.55, 0.95)': 'Ease-in-out Sine.',
|
||||
'cubic-bezier(0.39, 0.575, 0.565, 1)': 'Ease-out Sine.',
|
||||
'cubic-bezier(0.55, 0.085, 0.68, 0.53)': 'Ease-in Quadratic. Based on power of two.',
|
||||
'cubic-bezier(0.455, 0.03, 0.515, 0.955)': 'Ease-in-out Quadratic. Based on power of two.',
|
||||
'cubic-bezier(0.25, 0.46, 0.45, 0.94)': 'Ease-out Quadratic. Based on power of two.',
|
||||
'cubic-bezier(0.895, 0.03, 0.685, 0.22)': 'Ease-in Quartic. Based on power of four.',
|
||||
'cubic-bezier(0.77, 0, 0.175, 1)': 'Ease-in-out Quartic. Based on power of four.',
|
||||
'cubic-bezier(0.165, 0.84, 0.44, 1)': 'Ease-out Quartic. Based on power of four.',
|
||||
'cubic-bezier(0.755, 0.05, 0.855, 0.06)': 'Ease-in Quintic. Based on power of five.',
|
||||
'cubic-bezier(0.86, 0, 0.07, 1)': 'Ease-in-out Quintic. Based on power of five.',
|
||||
'cubic-bezier(0.23, 1, 0.320, 1)': 'Ease-out Quintic. Based on power of five.'
|
||||
};
|
||||
|
||||
export let basicShapeFunctions : { [name:string]:string } = {
|
||||
'circle()': 'Defines a circle.',
|
||||
'ellipse()': 'Defines an ellipse.',
|
||||
'inset()': 'Defines an inset rectangle.',
|
||||
'polygon()': 'Defines a polygon.'
|
||||
};
|
||||
|
||||
export let units : { [unitName:string]:string[] } = {
|
||||
'length': ['em', 'rem', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'vw', 'vh', 'vmin', 'vmax'],
|
||||
'angle': ['deg', 'rad', 'grad', 'turn'],
|
||||
'time': ['ms', 's'],
|
||||
'frequency': ['Hz', 'kHz'],
|
||||
'resolution': ['dpi', 'dpcm', 'dppx'],
|
||||
'percentage': ['%']
|
||||
};
|
||||
|
||||
export let html5Tags = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption',
|
||||
'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer',
|
||||
'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link',
|
||||
'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q',
|
||||
'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td',
|
||||
'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'let', 'video', 'wbr' ];
|
||||
|
||||
export let svgElements = ['circle', 'clipPath', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
|
||||
'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
|
||||
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'foreignObject', 'g', 'hatch', 'hatchpath', 'image', 'line', 'linearGradient',
|
||||
'marker', 'mask', 'mesh', 'meshpatch', 'meshrow', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'solidcolor', 'stop', 'svg', 'switch',
|
||||
'symbol', 'text', 'textPath', 'tspan', 'use', 'view'];
|
||||
|
||||
export function isColorConstructor(node:nodes.Function): boolean {
|
||||
let name = node.getName();
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return /^(rgb|rgba|hsl|hsla)$/gi.test(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is a color value - either
|
||||
* defined a hex number, as rgb or rgba function, or
|
||||
* as color name.
|
||||
*/
|
||||
export function isColorValue(node:nodes.Node):boolean {
|
||||
if(node.type === nodes.NodeType.HexColorValue) {
|
||||
return true;
|
||||
} else if(node.type === nodes.NodeType.Function) {
|
||||
return this.isColorConstructor(<nodes.Function> node);
|
||||
} else if (node.type === nodes.NodeType.Identifier) {
|
||||
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
|
||||
return false;
|
||||
}
|
||||
let candidateColor = node.getText().toLowerCase();
|
||||
if(candidateColor === 'none') {
|
||||
return false;
|
||||
}
|
||||
if (colors[candidateColor]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given name is a known property.
|
||||
*/
|
||||
export function isKnownProperty(name: string):boolean {
|
||||
if(!name) {
|
||||
return false;
|
||||
} else {
|
||||
name = name.toLowerCase();
|
||||
return getProperties().hasOwnProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
export function isCommonValue(entry:Value):boolean {
|
||||
return entry.browsers.count > 1;
|
||||
}
|
||||
|
||||
export function getPageBoxDirectives():string[] {
|
||||
return [
|
||||
'@bottom-center', '@bottom-left', '@bottom-left-corner', '@bottom-right', '@bottom-right-corner',
|
||||
'@left-bottom', '@left-middle', '@left-top', '@right-bottom', '@right-middle', '@right-top',
|
||||
'@top-center', '@top-left', '@top-left-corner', '@top-right', '@top-right-corner'
|
||||
];
|
||||
}
|
||||
|
||||
export function getEntryDescription(entry:{description: string; browsers: Browsers}): string {
|
||||
let desc = entry.description || '';
|
||||
let browserLabel = this.getBrowserLabel(entry.browsers);
|
||||
if (browserLabel) {
|
||||
if (desc) {
|
||||
desc = desc + '\n';
|
||||
}
|
||||
desc= desc + '(' + browserLabel + ')';
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
export function getBrowserLabel(b: Browsers): string {
|
||||
let result = '';
|
||||
if (!b || b.all || b.count === 0) {
|
||||
return null;
|
||||
}
|
||||
for (let curr in browserNames) {
|
||||
if (typeof (<any> b)[curr] === 'string') {
|
||||
if (result.length > 0) {
|
||||
result = result + ', ';
|
||||
}
|
||||
result = result + (<any> browserNames)[curr];
|
||||
let version = (<any> b)[curr];
|
||||
if (version.length > 0) {
|
||||
result = result + ' ' + version;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface Browsers {
|
||||
E?:string;
|
||||
FF?:string;
|
||||
IE?:string;
|
||||
O?:string;
|
||||
C?:string;
|
||||
S?:string;
|
||||
count:number;
|
||||
all:boolean;
|
||||
onCodeComplete:boolean;
|
||||
}
|
||||
|
||||
export interface Value {
|
||||
name:string;
|
||||
description:string;
|
||||
browsers:Browsers;
|
||||
}
|
||||
|
||||
export interface IEntry {
|
||||
name:string;
|
||||
restrictions:string[];
|
||||
browsers:Browsers;
|
||||
description:string;
|
||||
values:Value[];
|
||||
}
|
||||
|
||||
function evalBrowserEntry(browsers: string) {
|
||||
let browserEntry : Browsers = { all: false, count: 0, onCodeComplete: false};
|
||||
let count = 0;
|
||||
if (browsers) {
|
||||
browsers.split(',').forEach(
|
||||
(s: string) => {
|
||||
s = s.trim();
|
||||
if (s === 'all') {
|
||||
browserEntry.all= true;
|
||||
count = Number.MAX_VALUE;
|
||||
} else if (s !== 'none') {
|
||||
for (let key in browserNames) {
|
||||
if (s.indexOf(key) === 0) {
|
||||
(<any> browserEntry)[key] = s.substring(key.length).trim();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
browserEntry.all = true;
|
||||
count = Number.MAX_VALUE;
|
||||
}
|
||||
browserEntry.count = count;
|
||||
browserEntry.onCodeComplete = count > 0; // to be refined
|
||||
return browserEntry;
|
||||
}
|
||||
|
||||
|
||||
class ValueImpl implements Value {
|
||||
|
||||
private browserEntry: Browsers;
|
||||
|
||||
constructor(public data: any) {
|
||||
}
|
||||
|
||||
get name() : string {
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
get description() : string {
|
||||
return this.data.desc || browsers.descriptions[this.data.name];
|
||||
}
|
||||
|
||||
get browsers() : Browsers {
|
||||
if (!this.browserEntry) {
|
||||
this.browserEntry = evalBrowserEntry(this.data.browsers);
|
||||
}
|
||||
return this.browserEntry;
|
||||
}
|
||||
}
|
||||
|
||||
class EntryImpl implements IEntry {
|
||||
private browserEntry: Browsers;
|
||||
|
||||
constructor(public data: any) {
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.data.desc || browsers.descriptions[this.data.name];
|
||||
}
|
||||
|
||||
get browsers(): Browsers {
|
||||
if (!this.browserEntry) {
|
||||
this.browserEntry = evalBrowserEntry(this.data.browsers);
|
||||
}
|
||||
return this.browserEntry;
|
||||
}
|
||||
|
||||
get restrictions(): string[] {
|
||||
if (this.data.restriction) {
|
||||
return this.data.restriction.split(',').map(function(s: string) { return s.trim(); });
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
get values(): Value[] {
|
||||
if(!this.data.values) {
|
||||
return [];
|
||||
}
|
||||
if(!Array.isArray(this.data.values)) {
|
||||
return [new ValueImpl(this.data.values.value)];
|
||||
}
|
||||
return this.data.values.map(function (v: string) {
|
||||
return new ValueImpl(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let propertySet: { [key: string]: IEntry };
|
||||
let properties = browsers.data.css.properties;
|
||||
export function getProperties(): { [name: string]: IEntry; } {
|
||||
if(!propertySet) {
|
||||
propertySet = {
|
||||
};
|
||||
for(let i = 0, len = properties.length; i < len; i++) {
|
||||
let rawEntry = properties[i];
|
||||
propertySet[rawEntry.name] = new EntryImpl(rawEntry);
|
||||
}
|
||||
}
|
||||
return propertySet;
|
||||
}
|
||||
|
||||
let atDirectives = browsers.data.css.atdirectives;
|
||||
let atDirectiveList: IEntry[];
|
||||
export function getAtDirectives(): IEntry[] {
|
||||
if (!atDirectiveList) {
|
||||
atDirectiveList = [];
|
||||
for (let i = 0, len = atDirectives.length; i < len; i++) {
|
||||
let rawEntry = atDirectives[i];
|
||||
atDirectiveList.push(new EntryImpl(rawEntry));
|
||||
}
|
||||
}
|
||||
return atDirectiveList;
|
||||
}
|
||||
|
||||
|
||||
let pseudoElements = browsers.data.css.pseudoelements;
|
||||
let pseudoElementList: IEntry[];
|
||||
export function getPseudoElements(): IEntry[] {
|
||||
if (!pseudoElementList) {
|
||||
pseudoElementList = [];
|
||||
for (let i = 0, len = pseudoElements.length; i < len; i++) {
|
||||
let rawEntry = pseudoElements[i];
|
||||
pseudoClassesList.push(new EntryImpl(rawEntry));
|
||||
}
|
||||
}
|
||||
return pseudoElementList;
|
||||
}
|
||||
|
||||
let pseudoClasses = browsers.data.css.pseudoclasses;
|
||||
let pseudoClassesList: IEntry[];
|
||||
export function getPseudoClasses(): IEntry[]{
|
||||
if (!pseudoClassesList) {
|
||||
pseudoClassesList = [];
|
||||
for (let i = 0, len = pseudoClasses.length; i < len; i++) {
|
||||
let rawEntry = pseudoClasses[i];
|
||||
pseudoClassesList.push(new EntryImpl(rawEntry));
|
||||
}
|
||||
}
|
||||
return pseudoClassesList;
|
||||
}
|
||||
|
||||
export let browserNames = {
|
||||
E : 'Edge',
|
||||
FF : 'Firefox',
|
||||
S : 'Safari',
|
||||
C : 'Chrome',
|
||||
IE : 'IE',
|
||||
O : 'Opera'
|
||||
};
|
353
extensions/css/server/src/services/lessCompletion.ts
Normal file
353
extensions/css/server/src/services/lessCompletion.ts
Normal file
|
@ -0,0 +1,353 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as languageFacts from './languageFacts';
|
||||
import {CSSCompletion} from './cssCompletion';
|
||||
import {CompletionList, CompletionItemKind} from 'vscode-languageserver';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class LESSCompletion extends CSSCompletion {
|
||||
|
||||
private static builtInProposals = [
|
||||
{
|
||||
'name': 'escape',
|
||||
'example': 'escape(@string);',
|
||||
'description': localize('less.builtin.escape', 'URL encodes a string')
|
||||
},
|
||||
{
|
||||
'name': 'e',
|
||||
'example': 'e(@string);',
|
||||
'description': localize('less.builtin.e', 'escape string content')
|
||||
},
|
||||
{
|
||||
'name': 'replace',
|
||||
'example': 'replace(@string, @pattern, @replacement[, @flags]);',
|
||||
'description': localize('less.builtin.replace', 'string replace')
|
||||
},
|
||||
{
|
||||
'name': 'unit',
|
||||
'example': 'unit(@dimension, [@unit: \'\']);',
|
||||
'description': localize('less.builtin.unit', 'remove or change the unit of a dimension')
|
||||
},
|
||||
{
|
||||
'name': 'color',
|
||||
'example': 'color(@string);',
|
||||
'description': localize('less.builtin.color', 'parses a string to a color')
|
||||
},
|
||||
{
|
||||
'name': 'convert',
|
||||
'example': 'convert(@value, unit);',
|
||||
'description': localize('less.builtin.convert', 'converts numbers from one type into another')
|
||||
},
|
||||
{
|
||||
'name': 'data-uri',
|
||||
'example': 'data-uri([mimetype,] url);',
|
||||
'description': localize('less.builtin.data-uri', 'inlines a resource and falls back to `url()`')
|
||||
},
|
||||
{
|
||||
'name': 'length',
|
||||
'example': 'length(@list);',
|
||||
'description': localize('less.builtin.length', 'returns the number of elements in a value list')
|
||||
},
|
||||
{
|
||||
'name': 'extract',
|
||||
'example': 'extract(@list, index);',
|
||||
'description': localize('less.builtin.extract', 'returns a value at the specified position in the list')
|
||||
},
|
||||
{
|
||||
'name': 'abs',
|
||||
'description': localize('less.builtin.abs', 'absolute value of a number'),
|
||||
'example': 'abs(number);'
|
||||
},
|
||||
{
|
||||
'name': 'acos',
|
||||
'description': localize('less.builtin.acos', 'arccosine - inverse of cosine function'),
|
||||
'example': 'acos(number);'
|
||||
},
|
||||
{
|
||||
'name': 'asin',
|
||||
'description': localize('less.builtin.asin', 'arcsine - inverse of sine function'),
|
||||
'example': 'asin(number);'
|
||||
},
|
||||
{
|
||||
'name': 'ceil',
|
||||
'example': 'ceil(@number);',
|
||||
'description': localize('less.builtin.ceil', 'rounds up to an integer')
|
||||
},
|
||||
{
|
||||
'name': 'cos',
|
||||
'description': localize('less.builtin.cos', 'cosine function'),
|
||||
'example': 'cos(number);'
|
||||
},
|
||||
{
|
||||
'name': 'floor',
|
||||
'description': localize('less.builtin.floor', 'rounds down to an integer'),
|
||||
'example': 'floor(@number);'
|
||||
},
|
||||
{
|
||||
'name': 'percentage',
|
||||
'description': localize('less.builtin.percentage', 'converts to a %, e.g. 0.5 > 50%'),
|
||||
'example': 'percentage(@number);'
|
||||
},
|
||||
{
|
||||
'name': 'round',
|
||||
'description': localize('less.builtin.round', 'rounds a number to a number of places'),
|
||||
'example': 'round(number, [places: 0]);'
|
||||
},
|
||||
{
|
||||
'name': 'sqrt',
|
||||
'description': localize('less.builtin.sqrt', 'calculates square root of a number'),
|
||||
'example': 'sqrt(number);'
|
||||
},
|
||||
{
|
||||
'name': 'sin',
|
||||
'description': localize('less.builtin.sin', 'sine function'),
|
||||
'example': 'sin(number);'
|
||||
},
|
||||
{
|
||||
'name': 'tan',
|
||||
'description': localize('less.builtin.tan', 'tangent function'),
|
||||
'example': 'tan(number);'
|
||||
},
|
||||
{
|
||||
'name': 'atan',
|
||||
'description': localize('less.builtin.atan', 'arctangent - inverse of tangent function'),
|
||||
'example': 'atan(number);'
|
||||
},
|
||||
{
|
||||
'name': 'pi',
|
||||
'description': localize('less.builtin.pi', 'returns pi'),
|
||||
'example': 'pi();'
|
||||
},
|
||||
{
|
||||
'name': 'pow',
|
||||
'description': localize('less.builtin.pow', 'first argument raised to the power of the second argument'),
|
||||
'example': 'pow(@base, @exponent);'
|
||||
},
|
||||
{
|
||||
'name': 'mod',
|
||||
'description': localize('less.builtin.mod', 'first argument modulus second argument'),
|
||||
'example': 'mod(number, number);'
|
||||
},
|
||||
{
|
||||
'name': 'min',
|
||||
'description': localize('less.builtin.min', 'returns the lowest of one or more values'),
|
||||
'example': 'min(@x, @y);'
|
||||
},
|
||||
{
|
||||
'name': 'max',
|
||||
'description': localize('less.builtin.max', 'returns the lowest of one or more values'),
|
||||
'example': 'max(@x, @y);'
|
||||
}
|
||||
];
|
||||
|
||||
private static colorProposals = [
|
||||
{
|
||||
'name': 'argb',
|
||||
'example': 'argb(@color);',
|
||||
'description': localize('less.builtin.argb', 'creates a #AARRGGBB')
|
||||
},
|
||||
{
|
||||
'name': 'hsl',
|
||||
'example': 'hsl(@hue, @saturation, @lightness);',
|
||||
'description': localize('less.builtin.hsl', 'creates a color')
|
||||
},
|
||||
{
|
||||
'name': 'hsla',
|
||||
'example': 'hsla(@hue, @saturation, @lightness, @alpha);',
|
||||
'description': localize('less.builtin.hsla', 'creates a color')
|
||||
},
|
||||
{
|
||||
'name': 'hsv',
|
||||
'example': 'hsv(@hue, @saturation, @value);',
|
||||
'description': localize('less.builtin.hsv', 'creates a color')
|
||||
},
|
||||
{
|
||||
'name': 'hsva',
|
||||
'example': 'hsva(@hue, @saturation, @value, @alpha);',
|
||||
'description': localize('less.builtin.hsva', 'creates a color')
|
||||
},
|
||||
{
|
||||
'name': 'hue',
|
||||
'example': 'hue(@color);',
|
||||
'description': localize('less.builtin.hue', 'returns the `hue` channel of `@color` in the HSL space')
|
||||
},
|
||||
{
|
||||
'name': 'saturation',
|
||||
'example': 'saturation(@color);',
|
||||
'description': localize('less.builtin.saturation', 'returns the `saturation` channel of `@color` in the HSL space')
|
||||
},
|
||||
{
|
||||
'name': 'lightness',
|
||||
'example': 'lightness(@color);',
|
||||
'description': localize('less.builtin.lightness', 'returns the `lightness` channel of `@color` in the HSL space')
|
||||
},
|
||||
{
|
||||
'name': 'hsvhue',
|
||||
'example': 'hsvhue(@color);',
|
||||
'description': localize('less.builtin.hsvhue', 'returns the `hue` channel of `@color` in the HSV space')
|
||||
},
|
||||
{
|
||||
'name': 'hsvsaturation',
|
||||
'example': 'hsvsaturation(@color);',
|
||||
'description': localize('less.builtin.hsvsaturation', 'returns the `saturation` channel of `@color` in the HSV space')
|
||||
},
|
||||
{
|
||||
'name': 'hsvvalue',
|
||||
'example': 'hsvvalue(@color);',
|
||||
'description': localize('less.builtin.hsvvalue', 'returns the `value` channel of `@color` in the HSV space')
|
||||
},
|
||||
{
|
||||
'name': 'red',
|
||||
'example': 'red(@color);',
|
||||
'description': localize('less.builtin.red', 'returns the `red` channel of `@color`')
|
||||
},
|
||||
{
|
||||
'name': 'green',
|
||||
'example': 'green(@color);',
|
||||
'description': localize('less.builtin.green', 'returns the `green` channel of `@color`')
|
||||
},
|
||||
{
|
||||
'name': 'blue',
|
||||
'example': 'blue(@color);',
|
||||
'description': localize('less.builtin.blue', 'returns the `blue` channel of `@color`')
|
||||
},
|
||||
{
|
||||
'name': 'alpha',
|
||||
'example': 'alpha(@color);',
|
||||
'description': localize('less.builtin.alpha', 'returns the `alpha` channel of `@color`')
|
||||
},
|
||||
{
|
||||
'name': 'luma',
|
||||
'example': 'luma(@color);',
|
||||
'description': localize('less.builtin.luma', 'returns the `luma` value (perceptual brightness) of `@color`')
|
||||
},
|
||||
{
|
||||
'name': 'saturate',
|
||||
'example': 'saturate(@color, 10%);',
|
||||
'description': localize('less.builtin.saturate', 'return `@color` 10% points more saturated')
|
||||
},
|
||||
{
|
||||
'name': 'desaturate',
|
||||
'example': 'desaturate(@color, 10%);',
|
||||
'description': localize('less.builtin.desaturate', 'return `@color` 10% points less saturated')
|
||||
},
|
||||
{
|
||||
'name': 'lighten',
|
||||
'example': 'lighten(@color, 10%);',
|
||||
'description': localize('less.builtin.lighten', 'return `@color` 10% points lighter')
|
||||
},
|
||||
{
|
||||
'name': 'darken',
|
||||
'example': 'darken(@color, 10%);',
|
||||
'description': localize('less.builtin.darken', 'return `@color` 10% points darker')
|
||||
},
|
||||
{
|
||||
'name': 'fadein',
|
||||
'example': 'fadein(@color, 10%);',
|
||||
'description': localize('less.builtin.fadein', 'return `@color` 10% points less transparent')
|
||||
},
|
||||
{
|
||||
'name': 'fadeout',
|
||||
'example': 'fadeout(@color, 10%);',
|
||||
'description': localize('less.builtin.fadeout', 'return `@color` 10% points more transparent')
|
||||
},
|
||||
{
|
||||
'name': 'fade',
|
||||
'example': 'fade(@color, 50%);',
|
||||
'description': localize('less.builtin.fade', 'return `@color` with 50% transparency')
|
||||
},
|
||||
{
|
||||
'name': 'spin',
|
||||
'example': 'spin(@color, 10);',
|
||||
'description': localize('less.builtin.spin', 'return `@color` with a 10 degree larger in hue')
|
||||
},
|
||||
{
|
||||
'name': 'mix',
|
||||
'example': 'mix(@color1, @color2, [@weight: 50%]);',
|
||||
'description': localize('less.builtin.mix', 'return a mix of `@color1` and `@color2`')
|
||||
},
|
||||
{
|
||||
'name': 'greyscale',
|
||||
'example': 'greyscale(@color);',
|
||||
'description': localize('less.builtin.greyscale', 'returns a grey, 100% desaturated color')
|
||||
},
|
||||
{
|
||||
'name': 'contrast',
|
||||
'example': 'contrast(@color1, [@darkcolor: black], [@lightcolor: white], [@threshold: 43%]);',
|
||||
'description': localize('less.builtin.contrast', 'return `@darkcolor` if `@color1 is> 43% luma` otherwise return `@lightcolor`, see notes')
|
||||
},
|
||||
{
|
||||
'name': 'multiply',
|
||||
'example': 'multiply(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'screen',
|
||||
'example': 'screen(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'overlay',
|
||||
'example': 'overlay(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'softlight',
|
||||
'example': 'softlight(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'hardlight',
|
||||
'example': 'hardlight(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'difference',
|
||||
'example': 'difference(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'exclusion',
|
||||
'example': 'exclusion(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'average',
|
||||
'example': 'average(@color1, @color2);'
|
||||
},
|
||||
{
|
||||
'name': 'negation',
|
||||
'example': 'negation(@color1, @color2);'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
super('@');
|
||||
}
|
||||
|
||||
private createFunctionProposals(proposals: { name: string; example: string; description?: string; }[], result: CompletionList): CompletionList {
|
||||
proposals.forEach(p => {
|
||||
result.items.push({
|
||||
label: p.name,
|
||||
detail: p.example,
|
||||
documentation: p.description,
|
||||
insertText: p.name + '({{}})',
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public getTermProposals(result: CompletionList): CompletionList {
|
||||
this.createFunctionProposals(LESSCompletion.builtInProposals, result);
|
||||
return super.getTermProposals(result);
|
||||
}
|
||||
|
||||
protected getColorProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
this.createFunctionProposals(LESSCompletion.colorProposals, result);
|
||||
return super.getColorProposals(entry, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
536
extensions/css/server/src/services/lint.ts
Normal file
536
extensions/css/server/src/services/lint.ts
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as languageFacts from './languageFacts';
|
||||
import {Rules, ILintConfigurationSettings, toLevel, Rule} from './lintRules';
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class Element {
|
||||
|
||||
public name: string;
|
||||
public node: nodes.Declaration;
|
||||
|
||||
constructor(text: string, data: nodes.Declaration) {
|
||||
this.name = text;
|
||||
this.node = data;
|
||||
}
|
||||
}
|
||||
|
||||
class NodesByRootMap {
|
||||
public data: { [name: string]: { nodes: nodes.Node[]; names: string[] } } = {};
|
||||
|
||||
public add(root: string, name: string, node: nodes.Node): void {
|
||||
let entry = this.data[root];
|
||||
if (!entry) {
|
||||
entry = { nodes: [], names: [] };
|
||||
this.data[root] = entry;
|
||||
}
|
||||
entry.names.push(name);
|
||||
if (node) {
|
||||
entry.nodes.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LintVisitor implements nodes.IVisitor {
|
||||
|
||||
static entries(node: nodes.Node, settings: ILintConfigurationSettings): nodes.IMarker[] {
|
||||
let visitor = new LintVisitor(settings);
|
||||
node.accept(visitor);
|
||||
return visitor.getEntries();
|
||||
}
|
||||
|
||||
static prefixes = [
|
||||
'-ms-', '-moz-', '-o-', '-webkit-', // Quite common
|
||||
// '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
|
||||
];
|
||||
|
||||
private warnings: nodes.IMarker[] = [];
|
||||
private configuration: { [id: string]: nodes.Level };
|
||||
|
||||
constructor(settings: ILintConfigurationSettings = {}) {
|
||||
this.configuration = {};
|
||||
for (let ruleKey in Rules) {
|
||||
let rule = Rules[ruleKey];
|
||||
let level = settings[rule.id] || toLevel(rule.defaultValue);
|
||||
this.configuration[rule.id] = level;
|
||||
}
|
||||
}
|
||||
|
||||
private fetch(input: Element[], s: string): Element[] {
|
||||
let elements: Element[] = [];
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (input[i].name.toLowerCase() === s) {
|
||||
elements.push(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private fetchWithValue(input: Element[], s: string, v: string): Element[] {
|
||||
let elements: Element[] = [];
|
||||
for (let inputElement of input) {
|
||||
if (inputElement.name.toLowerCase() === s) {
|
||||
let expression = inputElement.node.getValue();
|
||||
if (expression && this.findValueInExpression(expression, v)) {
|
||||
elements.push(inputElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
private findValueInExpression(expression: nodes.Expression, v: string): boolean {
|
||||
let found = false;
|
||||
expression.accept(node => {
|
||||
if (node.type === nodes.NodeType.Identifier && node.getText() === v) {
|
||||
found = true;
|
||||
}
|
||||
return !found;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
private fetchWithin(input: Element[], s: string): Element[] {
|
||||
let elements: Element[] = [];
|
||||
|
||||
for (let inputElement of input) {
|
||||
if (inputElement.name.toLowerCase().indexOf(s) >= 0) {
|
||||
elements.push(inputElement);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public getEntries(filter: number = (nodes.Level.Warning | nodes.Level.Error)): nodes.IMarker[] {
|
||||
return this.warnings.filter(entry => {
|
||||
return (entry.getLevel() & filter) !== 0;
|
||||
});
|
||||
}
|
||||
|
||||
private addEntry(node: nodes.Node, rule: Rule, details?: string): void {
|
||||
let entry = new nodes.Marker(node, rule, this.configuration[rule.id], details);
|
||||
this.warnings.push(entry);
|
||||
}
|
||||
|
||||
private getMissingNames(expected: string[], actual: string[]): string {
|
||||
expected = expected.slice(0); // clone
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
let k = expected.indexOf(actual[i]);
|
||||
if (k !== -1) {
|
||||
expected[k] = null;
|
||||
}
|
||||
}
|
||||
let result: string = null;
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
let curr = expected[i];
|
||||
if (curr) {
|
||||
if (result === null) {
|
||||
result = localize('namelist.single', "'{0}'", curr);
|
||||
} else {
|
||||
result = localize('namelist.concatenated', "{0}, '{1}'", result, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
switch (node.type) {
|
||||
case nodes.NodeType.Stylesheet:
|
||||
return this.visitStylesheet(<nodes.Stylesheet>node);
|
||||
case nodes.NodeType.FontFace:
|
||||
return this.visitFontFace(<nodes.FontFace>node);
|
||||
case nodes.NodeType.Ruleset:
|
||||
return this.visitRuleSet(<nodes.RuleSet>node);
|
||||
case nodes.NodeType.SimpleSelector:
|
||||
return this.visitSimpleSelector(<nodes.SimpleSelector>node);
|
||||
case nodes.NodeType.Function:
|
||||
return this.visitFunction(<nodes.Function>node);
|
||||
case nodes.NodeType.NumericValue:
|
||||
return this.visitNumericValue(<nodes.NumericValue>node);
|
||||
case nodes.NodeType.Import:
|
||||
return this.visitImport(<nodes.Import>node);
|
||||
}
|
||||
return this.visitUnknownNode(node);
|
||||
}
|
||||
|
||||
private visitStylesheet(node: nodes.Stylesheet): boolean {
|
||||
// @keyframe and it's vendor specific alternatives
|
||||
// @keyframe should be included
|
||||
|
||||
let keyframes = new NodesByRootMap();
|
||||
node.accept(node => {
|
||||
if (node instanceof nodes.Keyframe) {
|
||||
let keyword = (<nodes.Keyframe>node).getKeyword();
|
||||
let text = keyword.getText();
|
||||
keyframes.add((<nodes.Keyframe>node).getName(), text, (text !== '@keyframes') ? keyword : null);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
let expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
|
||||
|
||||
for (let name in keyframes.data) {
|
||||
let actual = keyframes.data[name].names;
|
||||
let needsStandard = (actual.indexOf('@keyframes') === -1);
|
||||
if (!needsStandard && actual.length === 1) {
|
||||
continue; // only the non-vendor specific keyword is used, that's fine, no warning
|
||||
}
|
||||
let addVendorSpecificWarnings = (node: nodes.Node) => {
|
||||
if (needsStandard) {
|
||||
let message = localize('keyframes.standardrule.missing', "Always define standard rule '@keyframes' when defining keyframes.");
|
||||
this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
|
||||
}
|
||||
if (missingVendorSpecific) {
|
||||
let message = localize('keyframes.vendorspecific.missing', "Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
|
||||
this.addEntry(node, Rules.AllVendorPrefixes, message);
|
||||
}
|
||||
};
|
||||
|
||||
let missingVendorSpecific = this.getMissingNames(expected, actual);
|
||||
if (missingVendorSpecific || needsStandard) {
|
||||
keyframes.data[name].nodes.forEach(addVendorSpecificWarnings);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitSimpleSelector(node: nodes.SimpleSelector): boolean {
|
||||
|
||||
let text = node.getText();
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Lint - The universal selector (*) is known to be slow.
|
||||
/////////////////////////////////////////////////////////////
|
||||
if (text === '*') {
|
||||
this.addEntry(node, Rules.UniversalSelector);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Lint - Avoid id selectors
|
||||
/////////////////////////////////////////////////////////////
|
||||
if (text.indexOf('#') === 0) {
|
||||
this.addEntry(node, Rules.AvoidIdSelector);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitImport(node: nodes.Import): boolean {
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
|
||||
/////////////////////////////////////////////////////////////
|
||||
this.addEntry(node, Rules.ImportStatemement);
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitRuleSet(node: nodes.RuleSet): boolean {
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Lint - Don't use empty rulesets.
|
||||
/////////////////////////////////////////////////////////////
|
||||
let declarations = node.getDeclarations();
|
||||
if (!declarations) {
|
||||
// syntax error
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!declarations.hasChildren()) {
|
||||
this.addEntry(node.getSelectors(), Rules.EmptyRuleSet);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let propertyTable: Element[] = [];
|
||||
declarations.getChildren().forEach(function (element) {
|
||||
if (element instanceof nodes.Declaration) {
|
||||
let decl = <nodes.Declaration>element;
|
||||
propertyTable.push(new Element(decl.getFullPropertyName(), decl));
|
||||
}
|
||||
}, this);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Don't use width or height when using padding or border.
|
||||
/////////////////////////////////////////////////////////////
|
||||
if ((this.fetch(propertyTable, 'width').length > 0 || this.fetch(propertyTable, 'height').length > 0) && (this.fetchWithin(propertyTable, 'padding').length > 0 || this.fetchWithin(propertyTable, 'border').length > 0)) {
|
||||
let elements: Element[] = this.fetch(propertyTable, 'width');
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
this.addEntry(elements[index].node, Rules.NoWidthOrHeightWhenPaddingOrBorder);
|
||||
}
|
||||
elements = this.fetch(propertyTable, 'height');
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
this.addEntry(elements[index].node, Rules.NoWidthOrHeightWhenPaddingOrBorder);
|
||||
}
|
||||
elements = this.fetchWithin(propertyTable, 'padding');
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
this.addEntry(elements[index].node, Rules.NoWidthOrHeightWhenPaddingOrBorder);
|
||||
}
|
||||
elements = this.fetchWithin(propertyTable, 'border');
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
this.addEntry(elements[index].node, Rules.NoWidthOrHeightWhenPaddingOrBorder);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Properties ignored due to display
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
// With 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect
|
||||
let displayElems = this.fetchWithValue(propertyTable, 'display', 'inline');
|
||||
if (displayElems.length > 0) {
|
||||
['width', 'height', 'margin-top', 'margin-bottom', 'float'].forEach(function (prop) {
|
||||
let elem = self.fetch(propertyTable, prop);
|
||||
for (let index = 0; index < elem.length; index++) {
|
||||
self.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// With 'display: inline-block', 'float' has no effect
|
||||
displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
|
||||
if (displayElems.length > 0) {
|
||||
let elem = this.fetch(propertyTable, 'float');
|
||||
for (let index = 0; index < elem.length; index++) {
|
||||
this.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
// With 'display: block', 'vertical-align' has no effect
|
||||
displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
|
||||
if (displayElems.length > 0) {
|
||||
let elem = this.fetch(propertyTable, 'vertical-align');
|
||||
for (let index = 0; index < elem.length; index++) {
|
||||
this.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Don't use !important
|
||||
/////////////////////////////////////////////////////////////
|
||||
node.accept(n => {
|
||||
if (n.type === nodes.NodeType.Prio) {
|
||||
self.addEntry(n, Rules.AvoidImportant);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Avoid 'float'
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
let elements: Element[] = this.fetch(propertyTable, 'float');
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
this.addEntry(elements[index].node, Rules.AvoidFloat);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Don't use duplicate declarations.
|
||||
/////////////////////////////////////////////////////////////
|
||||
for (let i = 0; i < propertyTable.length; i++) {
|
||||
let element = propertyTable[i];
|
||||
if (element.name.toLowerCase() !== 'background') {
|
||||
let value = element.node.getValue();
|
||||
if (value && value.getText()[0] !== '-') {
|
||||
let elements = this.fetch(propertyTable, element.name);
|
||||
if (elements.length > 1) {
|
||||
for (let k = 0; k < elements.length; k++) {
|
||||
let value = elements[k].node.getValue();
|
||||
if (value && value.getText()[0] !== '-' && elements[k] !== element) {
|
||||
this.addEntry(element.node, Rules.DuplicateDeclarations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
let propertiesBySuffix = new NodesByRootMap();
|
||||
let containsUnknowns = false;
|
||||
|
||||
declarations.getChildren().forEach(node => {
|
||||
if (this.isCSSDeclaration(node)) {
|
||||
let decl = <nodes.Declaration>node;
|
||||
let name = decl.getFullPropertyName();
|
||||
let firstChar = name.charAt(0);
|
||||
|
||||
if (firstChar === '-') {
|
||||
if (name.charAt(1) !== '-') { // avoid css variables
|
||||
if (!languageFacts.isKnownProperty(name)) {
|
||||
this.addEntry(decl.getProperty(), Rules.UnknownVendorSpecificProperty);
|
||||
}
|
||||
let nonPrefixedName = decl.getNonPrefixedPropertyName();
|
||||
propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
|
||||
}
|
||||
} else {
|
||||
if (firstChar === '*' || firstChar === '_') {
|
||||
this.addEntry(decl.getProperty(), Rules.IEStarHack);
|
||||
name = name.substr(1);
|
||||
}
|
||||
if (!languageFacts.isKnownProperty(name)) {
|
||||
this.addEntry(decl.getProperty(), Rules.UnknownProperty);
|
||||
}
|
||||
propertiesBySuffix.add(name, name, null); // don't pass the node as we don't show errors on the standard
|
||||
}
|
||||
} else {
|
||||
containsUnknowns = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!containsUnknowns) { // don't perform this test if there are
|
||||
for (let suffix in propertiesBySuffix.data) {
|
||||
let entry = propertiesBySuffix.data[suffix];
|
||||
let actual = entry.names;
|
||||
|
||||
let needsStandard = languageFacts.isKnownProperty(suffix) && (actual.indexOf(suffix) === -1);
|
||||
if (!needsStandard && actual.length === 1) {
|
||||
continue; // only the non-vendor specific rule is used, that's fine, no warning
|
||||
}
|
||||
|
||||
let expected: string[] = [];
|
||||
for (let i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
|
||||
let prefix = LintVisitor.prefixes[i];
|
||||
if (languageFacts.isKnownProperty(prefix + suffix)) {
|
||||
expected.push(prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
let addVendorSpecificWarnings = (node: nodes.Node) => {
|
||||
if (needsStandard) {
|
||||
let message = localize('property.standard.missing', "Also define the standard property '{0}' for compatibility", suffix);
|
||||
this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
|
||||
}
|
||||
if (missingVendorSpecific) {
|
||||
let message = localize('property.vendorspecific.missing', "Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
|
||||
this.addEntry(node, Rules.AllVendorPrefixes, message);
|
||||
}
|
||||
};
|
||||
|
||||
let missingVendorSpecific = this.getMissingNames(expected, actual);
|
||||
if (missingVendorSpecific || needsStandard) {
|
||||
entry.nodes.forEach(addVendorSpecificWarnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitNumericValue(node: nodes.NumericValue): boolean {
|
||||
/////////////////////////////////////////////////////////////
|
||||
// 0 has no following unit
|
||||
/////////////////////////////////////////////////////////////
|
||||
let value = node.getValue();
|
||||
if (value.unit === '%') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parseFloat(value.value) === 0.0 && !!value.unit) {
|
||||
this.addEntry(node, Rules.ZeroWithUnit);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitFontFace(node: nodes.FontFace): boolean {
|
||||
let declarations = node.getDeclarations();
|
||||
if (!declarations) {
|
||||
// syntax error
|
||||
return;
|
||||
}
|
||||
|
||||
let definesSrc = false, definesFontFamily = false;
|
||||
let containsUnknowns = false;
|
||||
declarations.getChildren().forEach(node => {
|
||||
if (this.isCSSDeclaration(node)) {
|
||||
let name = ((<nodes.Declaration>node).getProperty().getName().toLocaleLowerCase());
|
||||
if (name === 'src') { definesSrc = true; }
|
||||
if (name === 'font-family') { definesFontFamily = true; }
|
||||
} else {
|
||||
containsUnknowns = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
|
||||
this.addEntry(node, Rules.RequiredPropertiesForFontFace);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private isCSSDeclaration(node: nodes.Node): boolean {
|
||||
if (node instanceof nodes.Declaration) {
|
||||
if (!(<nodes.Declaration>node).getValue()) {
|
||||
return false;
|
||||
}
|
||||
let property = (<nodes.Declaration>node).getProperty();
|
||||
if (!property || property.getIdentifier().containsInterpolation()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private visitUnknownNode(node: nodes.Node): boolean {
|
||||
|
||||
// Rule: #eeff00 or #ef0
|
||||
if (node.type === nodes.NodeType.HexColorValue) {
|
||||
let text = node.getText();
|
||||
if (text.length !== 7 && text.length !== 4) {
|
||||
this.addEntry(node, Rules.HexColorLength);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private visitFunction(node: nodes.Function): boolean {
|
||||
|
||||
let fnName = node.getName().toLowerCase();
|
||||
let expectedAttrCount = -1;
|
||||
let actualAttrCount = 0;
|
||||
|
||||
switch (fnName) {
|
||||
case 'rgb(':
|
||||
case 'hsl(':
|
||||
expectedAttrCount = 3;
|
||||
break;
|
||||
case 'rgba(':
|
||||
case 'hsla(':
|
||||
expectedAttrCount = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
if (expectedAttrCount !== -1) {
|
||||
node.getArguments().accept(n => {
|
||||
if (n instanceof nodes.BinaryExpression) {
|
||||
actualAttrCount += 1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (actualAttrCount !== expectedAttrCount) {
|
||||
this.addEntry(node, Rules.ArgsInColorFunction);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
66
extensions/css/server/src/services/lintRules.ts
Normal file
66
extensions/css/server/src/services/lintRules.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
let Warning = 'warning';
|
||||
let Error = 'error';
|
||||
let Ignore = 'ignore';
|
||||
|
||||
export class Rule implements nodes.IRule {
|
||||
|
||||
public constructor(public id: string, public message: string, public defaultValue: string) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
export let Rules = {
|
||||
AllVendorPrefixes: new Rule('compatibleVendorPrefixes', localize('rule.vendorprefixes.all', "When using a vendor-specific prefix make sure to also include all other vendor-specific properties"), Ignore),
|
||||
IncludeStandardPropertyWhenUsingVendorPrefix: new Rule('vendorPrefix', localize('rule.standardvendorprefix.all', "When using a vendor-specific prefix also include the standard property"), Warning),
|
||||
DuplicateDeclarations: new Rule('duplicateProperties', localize('rule.duplicateDeclarations', "Do not use duplicate style definitions"), Ignore),
|
||||
EmptyRuleSet: new Rule('emptyRules', localize('rule.emptyRuleSets', "Do not use empty rulesets"), Warning),
|
||||
ImportStatemement: new Rule('importStatement', localize('rule.importDirective', "Import statements do not load in parallel"), Ignore),
|
||||
NoWidthOrHeightWhenPaddingOrBorder: new Rule('boxModel', localize('rule.withHeightAndBorderPadding', "Do not use width or height when using padding or border"), Ignore),
|
||||
UniversalSelector: new Rule('universalSelector', localize('rule.universalSelector', "The universal selector (*) is known to be slow"), Ignore),
|
||||
ZeroWithUnit: new Rule('zeroUnits', localize('rule.zeroWidthUnit', "No unit for zero needed"), Ignore),
|
||||
RequiredPropertiesForFontFace: new Rule('fontFaceProperties', localize('rule.fontFaceProperties', "@font-face rule must define 'src' and 'font-family' properties"), Warning),
|
||||
HexColorLength: new Rule('hexColorLength', localize('rule.hexColor', "Hex colors must consist of three or six hex numbers"), Error),
|
||||
ArgsInColorFunction: new Rule('argumentsInColorFunction', localize('rule.colorFunction', "Invalid number of parameters"), Error),
|
||||
UnknownProperty: new Rule('unknownProperties', localize('rule.unknownProperty', "Unknown property."), Warning),
|
||||
IEStarHack: new Rule('ieHack', localize('rule.ieHack', "IE hacks are only necessary when supporting IE7 and older"), Ignore),
|
||||
UnknownVendorSpecificProperty: new Rule('unknownVendorSpecificProperties', localize('rule.unknownVendorSpecificProperty', "Unknown vendor specific property."), Ignore),
|
||||
PropertyIgnoredDueToDisplay: new Rule('propertyIgnoredDueToDisplay', localize('rule.propertyIgnoredDueToDisplay', "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect"), Warning),
|
||||
AvoidImportant: new Rule('important', localize('rule.avoidImportant', "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored."), Ignore),
|
||||
AvoidFloat: new Rule('float', localize('rule.avoidFloat', "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes."), Ignore),
|
||||
AvoidIdSelector: new Rule('idSelector', localize('rule.avoidIdSelector', "Selectors should not contain IDs because these rules are too tightly coupled with the HTML."), Ignore),
|
||||
};
|
||||
|
||||
export interface ILintConfigurationSettings {
|
||||
[ruleId:string] : nodes.Level;
|
||||
}
|
||||
|
||||
export function sanitize(conf:any): ILintConfigurationSettings {
|
||||
let settings: ILintConfigurationSettings = {};
|
||||
for (let ruleName in Rules) {
|
||||
let rule = Rules[ruleName];
|
||||
let level = toLevel(conf[rule.id]);
|
||||
if (level) {
|
||||
settings[rule.id] = level;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
export function toLevel(level: string):nodes.Level {
|
||||
switch (level) {
|
||||
case 'ignore': return nodes.Level.Ignore;
|
||||
case 'warning': return nodes.Level.Warning;
|
||||
case 'error': return nodes.Level.Error;
|
||||
}
|
||||
return null;
|
||||
}
|
158
extensions/css/server/src/services/scssCompletion.ts
Normal file
158
extensions/css/server/src/services/scssCompletion.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as languageFacts from './languageFacts';
|
||||
import {CSSCompletion} from './cssCompletion';
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import {CompletionList, CompletionItemKind} from 'vscode-languageserver';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SCSSCompletion extends CSSCompletion {
|
||||
|
||||
private static variableDefaults: { [key: string]: string; } = {
|
||||
'$red': '1',
|
||||
'$green': '2',
|
||||
'$blue': '3',
|
||||
'$alpha': '1.0',
|
||||
'$color': '$color',
|
||||
'$weight': '0.5',
|
||||
'$hue': '0',
|
||||
'$saturation': '0%',
|
||||
'$lightness': '0%',
|
||||
'$degrees': '0',
|
||||
'$amount': '0',
|
||||
'$string': '""',
|
||||
'$substring': '"s"',
|
||||
'$number': '0',
|
||||
'$limit': '1'
|
||||
};
|
||||
|
||||
private static colorProposals = [
|
||||
{ func: 'red($color)', desc: localize('scss.builtin.red', 'Gets the red component of a color.') },
|
||||
{ func: 'green($color)', desc: localize('scss.builtin.green', 'Gets the green component of a color.') },
|
||||
{ func: 'blue($color)', desc: localize('scss.builtin.blue', 'Gets the blue component of a color.') },
|
||||
{ func: 'mix($color, $color, [$weight])', desc: localize('scss.builtin.mix', 'Mixes two colors together.') },
|
||||
{ func: 'hue($color)', desc: localize('scss.builtin.hue', 'Gets the hue component of a color.') },
|
||||
{ func: 'saturation($color)', desc: localize('scss.builtin.saturation', 'Gets the saturation component of a color.') },
|
||||
{ func: 'lightness($color)', desc: localize('scss.builtin.lightness', 'Gets the lightness component of a color.') },
|
||||
{ func: 'adjust-hue($color, $degrees)', desc: localize('scss.builtin.adjust-hue', 'Changes the hue of a color.') },
|
||||
{ func: 'lighten($color, $amount)', desc: localize('scss.builtin.lighten', 'Makes a color lighter.') },
|
||||
{ func: 'darken($color, $amount)', desc: localize('scss.builtin.darken', 'Makes a color darker.') },
|
||||
{ func: 'saturate($color, $amount)', desc: localize('scss.builtin.saturate', 'Makes a color more saturated.') },
|
||||
{ func: 'desaturate($color, $amount)', desc: localize('scss.builtin.desaturate', 'Makes a color less saturated.') },
|
||||
{ func: 'grayscale($color)', desc: localize('scss.builtin.grayscale', 'Converts a color to grayscale.') },
|
||||
{ func: 'complement($color)', desc: localize('scss.builtin.complement', 'Returns the complement of a color.') },
|
||||
{ func: 'invert($color)', desc: localize('scss.builtin.invert', 'Returns the inverse of a color.') },
|
||||
{ func: 'alpha($color)', desc: localize('scss.builtin.alpha', 'Gets the opacity component of a color.') },
|
||||
{ func: 'opacity($color)', desc: 'Gets the alpha component (opacity) of a color.' },
|
||||
{ func: 'rgba($color, $alpha)', desc: localize('scss.builtin.rgba', 'Changes the alpha component for a color.') },
|
||||
{ func: 'opacify($color, $amount)', desc: localize('scss.builtin.opacify', 'Makes a color more opaque.') },
|
||||
{ func: 'fade-in($color, $amount)', desc: localize('scss.builtin.fade-in', 'Makes a color more opaque.') },
|
||||
{ func: 'transparentize($color, $amount) / fade-out($color, $amount)', desc: localize('scss.builtin.transparentize', 'Makes a color more transparent.') },
|
||||
{ func: 'adjust-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: localize('scss.builtin.adjust-color', 'Increases or decreases one or more components of a color.') },
|
||||
{ func: 'scale-color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])', desc: localize('scss.builtin.scale-color', 'Fluidly scales one or more properties of a color.') },
|
||||
{ func: 'change-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: localize('scss.builtin.change-color', 'Changes one or more properties of a color.') },
|
||||
{ func: 'ie-hex-str($color)', desc: localize('scss.builtin.ie-hex-str', 'Converts a color into the format understood by IE filters.') }
|
||||
];
|
||||
|
||||
private static selectorFuncs = [
|
||||
{ func: 'selector-nest($selectors…)', desc: localize('scss.builtin.selector-nest', 'Nests selector beneath one another like they would be nested in the stylesheet.') },
|
||||
{ func: 'selector-append($selectors…)', desc: localize('scss.builtin.selector-append', 'Appends selectors to one another without spaces in between.') },
|
||||
{ func: 'selector-extend($selector, $extendee, $extender)', desc: localize('scss.builtin.selector-extend', 'Extends $extendee with $extender within $selector.') },
|
||||
{ func: 'selector-replace($selector, $original, $replacement)', desc: localize('scss.builtin.selector-replace', 'Replaces $original with $replacement within $selector.') },
|
||||
{ func: 'selector-unify($selector1, $selector2)', desc: localize('scss.builtin.selector-unify', 'Unifies two selectors to produce a selector that matches elements matched by both.') },
|
||||
{ func: 'is-superselector($super, $sub)', desc: localize('scss.builtin.is-superselector', 'Returns whether $super matches all the elements $sub does, and possibly more.') },
|
||||
{ func: 'simple-selectors($selector)', desc: localize('scss.builtin.simple-selectors', 'Returns the simple selectors that comprise a compound selector.') },
|
||||
{ func: 'selector-parse($selector)', desc: localize('scss.builtin.selector-parse', 'Parses a selector into the format returned by &.') }
|
||||
];
|
||||
|
||||
private static builtInFuncs = [
|
||||
{ func: 'unquote($string)', desc: localize('scss.builtin.unquote', 'Removes quotes from a string.') },
|
||||
{ func: 'quote($string)', desc: localize('scss.builtin.quote', 'Adds quotes to a string.') },
|
||||
{ func: 'str-length($string)', desc: localize('scss.builtin.str-length', 'Returns the number of characters in a string.') },
|
||||
{ func: 'str-insert($string, $insert, $index)', desc: localize('scss.builtin.str-insert', 'Inserts $insert into $string at $index.') },
|
||||
{ func: 'str-index($string, $substring)', desc: localize('scss.builtin.str-index', 'Returns the index of the first occurance of $substring in $string.') },
|
||||
{ func: 'str-slice($string, $start-at, [$end-at])', desc: localize('scss.builtin.str-slice', 'Extracts a substring from $string.') },
|
||||
{ func: 'to-upper-case($string)', desc: localize('scss.builtin.to-upper-case', 'Converts a string to upper case.') },
|
||||
{ func: 'to-lower-case($string)', desc: localize('scss.builtin.to-lower-case', 'Converts a string to lower case.') },
|
||||
{ func: 'percentage($number)', desc: localize('scss.builtin.percentage', 'Converts a unitless number to a percentage.') },
|
||||
{ func: 'round($number)', desc: localize('scss.builtin.round', 'Rounds a number to the nearest whole number.') },
|
||||
{ func: 'ceil($number)', desc: localize('scss.builtin.ceil', 'Rounds a number up to the next whole number.') },
|
||||
{ func: 'floor($number)', desc: localize('scss.builtin.floor', 'Rounds a number down to the previous whole number.') },
|
||||
{ func: 'abs($number)', desc: localize('scss.builtin.abs', 'Returns the absolute value of a number.') },
|
||||
{ func: 'min($numbers)', desc: localize('scss.builtin.min', 'Finds the minimum of several numbers.') },
|
||||
{ func: 'max($numbers)', desc: localize('scss.builtin.max', 'Finds the maximum of several numbers.') },
|
||||
{ func: 'random([$limit])', desc: localize('scss.builtin.random', 'Returns a random number.') },
|
||||
{ func: 'length($list)', desc: localize('scss.builtin.length', 'Returns the length of a list.') },
|
||||
{ func: 'nth($list, $n)', desc: localize('scss.builtin.nth', 'Returns a specific item in a list.') },
|
||||
{ func: 'set-nth($list, $n, $value)', desc: localize('scss.builtin.set-nth', 'Replaces the nth item in a list.') },
|
||||
{ func: 'join($list1, $list2, [$separator])', desc: localize('scss.builtin.join', 'Joins together two lists into one.') },
|
||||
{ func: 'append($list1, $val, [$separator])', desc: localize('scss.builtin.append', 'Appends a single value onto the end of a list.') },
|
||||
{ func: 'zip($lists)', desc: localize('scss.builtin.zip', 'Combines several lists into a single multidimensional list.') },
|
||||
{ func: 'index($list, $value)', desc: localize('scss.builtin.index', 'Returns the position of a value within a list.') },
|
||||
{ func: 'list-separator(#list)', desc: localize('scss.builtin.list-separator', 'Returns the separator of a list.') },
|
||||
{ func: 'map-get($map, $key)', desc: localize('scss.builtin.map-get', 'Returns the value in a map associated with a given key.') },
|
||||
{ func: 'map-merge($map1, $map2)', desc: localize('scss.builtin.map-merge', 'Merges two maps together into a new map.') },
|
||||
{ func: 'map-remove($map, $keys)', desc: localize('scss.builtin.map-remove', 'Returns a new map with keys removed.') },
|
||||
{ func: 'map-keys($map)', desc: localize('scss.builtin.map-keys', 'Returns a list of all keys in a map.') },
|
||||
{ func: 'map-values($map)', desc: localize('scss.builtin.map-values', 'Returns a list of all values in a map.') },
|
||||
{ func: 'map-has-key($map, $key)', desc: localize('scss.builtin.map-has-key', 'Returns whether a map has a value associated with a given key.') },
|
||||
{ func: 'keywords($args)', desc: localize('scss.builtin.keywords', 'Returns the keywords passed to a function that takes variable arguments.') },
|
||||
{ func: 'feature-exists($feature)', desc: localize('scss.builtin.feature-exists', 'Returns whether a feature exists in the current Sass runtime.') },
|
||||
{ func: 'variable-exists($name)', desc: localize('scss.builtin.variable-exists', 'Returns whether a variable with the given name exists in the current scope.') },
|
||||
{ func: 'global-variable-exists($name)', desc: localize('scss.builtin.global-variable-exists', 'Returns whether a variable with the given name exists in the global scope.') },
|
||||
{ func: 'function-exists($name)', desc: localize('scss.builtin.function-exists', 'Returns whether a function with the given name exists.') },
|
||||
{ func: 'mixin-exists($name)', desc: localize('scss.builtin.mixin-exists', 'Returns whether a mixin with the given name exists.') },
|
||||
{ func: 'inspect($value)', desc: localize('scss.builtin.inspect', 'Returns the string representation of a value as it would be represented in Sass.') },
|
||||
{ func: 'type-of($value)', desc: localize('scss.builtin.type-of', 'Returns the type of a value.') },
|
||||
{ func: 'unit($number)', desc: localize('scss.builtin.unit', 'Returns the unit(s) associated with a number.') },
|
||||
{ func: 'unitless($number)', desc: localize('scss.builtin.unitless', 'Returns whether a number has units.') },
|
||||
{ func: 'comparable($number1, $number2)', desc: localize('scss.builtin.comparable', 'Returns whether two numbers can be added, subtracted, or compared.') },
|
||||
{ func: 'call($name, $args…)', desc: localize('scss.builtin.call', 'Dynamically calls a Sass function.') }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super('$');
|
||||
}
|
||||
|
||||
private createFunctionProposals(proposals: {func: string; desc: string; }[], result: CompletionList): CompletionList {
|
||||
let replaceFunction = (match: string, p1: string) => p1 + ': {{' + (SCSSCompletion.variableDefaults[p1] || '') + '}}';
|
||||
proposals.forEach((p) => {
|
||||
result.items.push({
|
||||
label: p.func.substr(0, p.func.indexOf('(')),
|
||||
detail: p.func,
|
||||
documentation: p.desc,
|
||||
insertText: p.func.replace(/\[?(\$\w+)\]?/g, replaceFunction),
|
||||
kind: CompletionItemKind.Function
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public getCompletionsForSelector(ruleSet: nodes.RuleSet, result: CompletionList): CompletionList {
|
||||
this.createFunctionProposals(SCSSCompletion.selectorFuncs, result);
|
||||
return super.getCompletionsForSelector(ruleSet, result);
|
||||
}
|
||||
|
||||
public getTermProposals(result: CompletionList): CompletionList {
|
||||
this.createFunctionProposals(SCSSCompletion.builtInFuncs, result);
|
||||
return super.getTermProposals(result);
|
||||
}
|
||||
|
||||
protected getColorProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
|
||||
this.createFunctionProposals(SCSSCompletion.colorProposals, result);
|
||||
return super.getColorProposals(entry, result);
|
||||
}
|
||||
|
||||
public getCompletionsForDeclarationProperty(result: CompletionList): CompletionList {
|
||||
this.getCompletionsForSelector(null, result);
|
||||
return super.getCompletionsForDeclarationProperty(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
369
extensions/css/server/src/services/selectorPrinting.ts
Normal file
369
extensions/css/server/src/services/selectorPrinting.ts
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nodes from '../parser/cssNodes';
|
||||
import {MarkedString} from 'vscode-languageserver';
|
||||
|
||||
export class Element {
|
||||
|
||||
public name: string;
|
||||
public parent: Element;
|
||||
public children: Element[];
|
||||
public attributes: { [name: string]: string; };
|
||||
|
||||
public addChild(child: Element): void {
|
||||
if (child instanceof Element) {
|
||||
(<Element>child).parent = this;
|
||||
}
|
||||
if (!this.children) {
|
||||
this.children = [];
|
||||
}
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
public findRoot(): Element {
|
||||
let curr: Element = this;
|
||||
while (curr.parent && !(curr.parent instanceof RootElement)) {
|
||||
curr = curr.parent;
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
public removeChild(child: Element): boolean {
|
||||
if (this.children) {
|
||||
let index = this.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
this.children.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public addAttr(name: string, value: string): void {
|
||||
if (!this.attributes) {
|
||||
this.attributes = {};
|
||||
}
|
||||
if (this.attributes.hasOwnProperty(name)) {
|
||||
this.attributes[name] += ' ' + value;
|
||||
} else {
|
||||
this.attributes[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public clone(cloneChildren: boolean = true): Element {
|
||||
let elem = new Element();
|
||||
elem.name = this.name;
|
||||
if (this.attributes) {
|
||||
elem.attributes = {};
|
||||
for (let key in this.attributes) {
|
||||
elem.addAttr(key, this.attributes[key]);
|
||||
}
|
||||
}
|
||||
if (cloneChildren && this.children) {
|
||||
elem.children = [];
|
||||
for (let index = 0; index < this.children.length; index++) {
|
||||
elem.addChild(this.children[index].clone());
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
public cloneWithParent(): Element {
|
||||
let clone = this.clone(false);
|
||||
if (this.parent && !(this.parent instanceof RootElement)) {
|
||||
let parentClone = this.parent.cloneWithParent();
|
||||
parentClone.addChild(clone);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
export class RootElement extends Element {
|
||||
|
||||
}
|
||||
|
||||
export class LabelElement extends Element {
|
||||
|
||||
constructor(label: string) {
|
||||
super();
|
||||
this.name = label;
|
||||
}
|
||||
}
|
||||
|
||||
class MarkedStringPrinter {
|
||||
|
||||
private result: MarkedString[];
|
||||
|
||||
constructor(public quote: string) {
|
||||
// empty
|
||||
}
|
||||
|
||||
public print(element: Element): MarkedString[] {
|
||||
this.result = [];
|
||||
if (element instanceof RootElement) {
|
||||
this.doPrint(element.children, 0);
|
||||
} else {
|
||||
this.doPrint([element], 0);
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
|
||||
private doPrint(elements: Element[], indent: number) {
|
||||
for (let element of elements) {
|
||||
this.doPrintElement(element, indent);
|
||||
if (element.children) {
|
||||
this.doPrint(element.children, indent + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private writeLine(level: number, content: string) {
|
||||
let indent = new Array(level).join(' ');
|
||||
this.result.push({ language: 'html', value: indent + content });
|
||||
}
|
||||
|
||||
private doPrintElement(element: Element, indent: number) {
|
||||
|
||||
// special case: a simple label
|
||||
if (element instanceof LabelElement) {
|
||||
this.writeLine(indent, element.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// the real deal
|
||||
let content = ['<'];
|
||||
|
||||
// element name
|
||||
if (element.name) {
|
||||
content.push(element.name);
|
||||
} else {
|
||||
content.push('element');
|
||||
}
|
||||
|
||||
// attributes
|
||||
if (element.attributes) {
|
||||
Object.keys(element.attributes).forEach((attr) => {
|
||||
|
||||
content.push(' ');
|
||||
content.push(attr);
|
||||
let value = element.attributes[attr];
|
||||
if (value) {
|
||||
content.push('=');
|
||||
content.push(quotes.ensure(value, this.quote));
|
||||
}
|
||||
});
|
||||
}
|
||||
content.push('>');
|
||||
|
||||
this.writeLine(indent, content.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace quotes {
|
||||
|
||||
export function ensure(value: string, which: string): string {
|
||||
return which + remove(value) + which;
|
||||
}
|
||||
|
||||
export function remove(value: string): string {
|
||||
let match = value.match(/^['"](.*)["']$/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function toElement(node: nodes.SimpleSelector, parentElement?: Element): Element {
|
||||
|
||||
let result = new Element();
|
||||
node.getChildren().forEach((child) => {
|
||||
switch (child.type) {
|
||||
case nodes.NodeType.SelectorCombinator:
|
||||
if (parentElement) {
|
||||
let segments = child.getText().split('&');
|
||||
if (segments.length === 1) {
|
||||
// should not happen
|
||||
result.name = segments[0];
|
||||
break;
|
||||
}
|
||||
result = parentElement.cloneWithParent();
|
||||
if (segments[0]) {
|
||||
let root = result.findRoot();
|
||||
root.name = segments[0] + root.name;
|
||||
}
|
||||
for (let i = 1; i < segments.length; i++) {
|
||||
if (i > 1) {
|
||||
let clone = parentElement.cloneWithParent();
|
||||
result.addChild(clone.findRoot());
|
||||
result = clone;
|
||||
}
|
||||
result.name += segments[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case nodes.NodeType.SelectorPlaceholder:
|
||||
case nodes.NodeType.ElementNameSelector:
|
||||
let text = child.getText();
|
||||
result.name = text === '*' ? 'element' : text;
|
||||
break;
|
||||
case nodes.NodeType.ClassSelector:
|
||||
result.addAttr('class', child.getText().substring(1));
|
||||
break;
|
||||
case nodes.NodeType.IdentifierSelector:
|
||||
result.addAttr('id', child.getText().substring(1));
|
||||
break;
|
||||
case nodes.NodeType.MixinDeclaration:
|
||||
result.addAttr('class', (<nodes.MixinDeclaration>child).getName());
|
||||
break;
|
||||
case nodes.NodeType.PseudoSelector:
|
||||
result.addAttr(child.getText(), '');
|
||||
break;
|
||||
case nodes.NodeType.AttributeSelector:
|
||||
let expr = <nodes.BinaryExpression>child.getChildren()[0];
|
||||
if (expr) {
|
||||
let value: string;
|
||||
if (expr.getRight()) {
|
||||
switch (expr.getOperator().getText()) {
|
||||
case '|=':
|
||||
// excatly or followed by -words
|
||||
value = `${quotes.remove(expr.getRight().getText())}-\u2026`;
|
||||
break;
|
||||
case '^=':
|
||||
// prefix
|
||||
value = `${quotes.remove(expr.getRight().getText())}\u2026`;
|
||||
break;
|
||||
case '$=':
|
||||
// suffix
|
||||
value = `\u2026${quotes.remove(expr.getRight().getText())}`;
|
||||
break;
|
||||
case '~=':
|
||||
// one of a list of words
|
||||
value = ` \u2026 ${quotes.remove(expr.getRight().getText())} \u2026 `;
|
||||
break;
|
||||
case '*=':
|
||||
// substring
|
||||
value = `\u2026${quotes.remove(expr.getRight().getText())}\u2026`;
|
||||
break;
|
||||
default:
|
||||
value = quotes.remove(expr.getRight().getText());
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.addAttr(expr.getLeft().getText(), value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function selectorToMarkedString(node: nodes.Selector): MarkedString[] {
|
||||
let root = selectorToElement(node);
|
||||
return new MarkedStringPrinter('"').print(root);
|
||||
}
|
||||
|
||||
export function simpleSelectorToMarkedString(node: nodes.SimpleSelector): MarkedString[] {
|
||||
let element = toElement(node);
|
||||
return new MarkedStringPrinter('"').print(element);
|
||||
}
|
||||
|
||||
class SelectorElementBuilder {
|
||||
|
||||
private prev: nodes.Node;
|
||||
private element: Element;
|
||||
|
||||
public constructor(element: Element) {
|
||||
this.prev = null;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
public processSelector(selector: nodes.Selector): void {
|
||||
let parentElement: Element = null;
|
||||
|
||||
if (!(this.element instanceof RootElement)) {
|
||||
if (selector.getChildren().some((c) => c.hasChildren() && c.getChild(0).type === nodes.NodeType.SelectorCombinator)) {
|
||||
let curr = this.element.findRoot();
|
||||
if (curr.parent instanceof RootElement) {
|
||||
parentElement = this.element;
|
||||
|
||||
this.element = curr.parent;
|
||||
this.element.removeChild(curr);
|
||||
this.prev = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selector.getChildren().forEach((selectorChild) => {
|
||||
if (selectorChild instanceof nodes.SimpleSelector) {
|
||||
if (this.prev instanceof nodes.SimpleSelector) {
|
||||
let labelElement = new LabelElement('\u2026');
|
||||
this.element.addChild(labelElement);
|
||||
this.element = labelElement;
|
||||
} else if (this.prev && (this.prev.matches('+') || this.prev.matches('~'))) {
|
||||
this.element = <Element>this.element.parent;
|
||||
}
|
||||
|
||||
if (this.prev && this.prev.matches('~')) {
|
||||
this.element.addChild(toElement(<nodes.SimpleSelector>selectorChild));
|
||||
this.element.addChild(new LabelElement('\u22EE'));
|
||||
}
|
||||
|
||||
let thisElement = toElement(<nodes.SimpleSelector>selectorChild, parentElement);
|
||||
let root = thisElement.findRoot();
|
||||
|
||||
this.element.addChild(root);
|
||||
this.element = thisElement;
|
||||
}
|
||||
|
||||
if (selectorChild instanceof nodes.SimpleSelector ||
|
||||
selectorChild.type === nodes.NodeType.SelectorCombinatorParent ||
|
||||
selectorChild.type === nodes.NodeType.SelectorCombinatorSibling ||
|
||||
selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings) {
|
||||
|
||||
this.prev = selectorChild;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isNewSelectorContext(node: nodes.Node): boolean {
|
||||
switch (node.type) {
|
||||
case nodes.NodeType.MixinDeclaration:
|
||||
case nodes.NodeType.Stylesheet:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function selectorToElement(node: nodes.Selector): Element {
|
||||
let root: Element = new RootElement();
|
||||
let parentRuleSets: nodes.RuleSet[] = [];
|
||||
|
||||
if (node.getParent() instanceof nodes.RuleSet) {
|
||||
let parent = node.getParent().getParent(); // parent of the selector's ruleset
|
||||
while (parent && !isNewSelectorContext(parent)) {
|
||||
if (parent instanceof nodes.RuleSet) {
|
||||
parentRuleSets.push(<nodes.RuleSet>parent);
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
let builder = new SelectorElementBuilder(root);
|
||||
|
||||
for (let i = parentRuleSets.length - 1; i >= 0; i--) {
|
||||
let selector = parentRuleSets[i].getSelectors().getChild(0);
|
||||
if (selector) {
|
||||
builder.processSelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
builder.processSelector(node);
|
||||
return root;
|
||||
}
|
84
extensions/css/server/src/stylesheetCache.ts
Normal file
84
extensions/css/server/src/stylesheetCache.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {TextDocument} from 'vscode-languageserver';
|
||||
import {Stylesheet} from './parser/cssNodes';
|
||||
|
||||
export interface StylesheetCache {
|
||||
getStylesheet(document: TextDocument): Stylesheet;
|
||||
onDocumentRemoved(document: TextDocument): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function getStylesheetCache(maxEntries: number, cleanupIntervalTimeInSec: number, parseStylesheet: (document: TextDocument) => Stylesheet) : StylesheetCache {
|
||||
let styleSheets: { [uri:string]: {version:number, languageId: string, cTime: number, stylesheet: Stylesheet}} = {};
|
||||
let nStyleSheets = 0;
|
||||
|
||||
let cleanupInterval = void 0;
|
||||
if (cleanupIntervalTimeInSec > 0) {
|
||||
cleanupInterval = setInterval(() => {
|
||||
let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
|
||||
let uris = Object.keys(styleSheets);
|
||||
for (let uri of uris) {
|
||||
let stylesheetInfo = styleSheets[uri];
|
||||
if (stylesheetInfo.cTime < cutoffTime) {
|
||||
delete styleSheets[uri];
|
||||
nStyleSheets--;
|
||||
}
|
||||
}
|
||||
}, cleanupIntervalTimeInSec * 1000);
|
||||
}
|
||||
|
||||
return {
|
||||
getStylesheet(document: TextDocument) {
|
||||
let version = document.version;
|
||||
let languageId = document.languageId;
|
||||
let stylesheetInfo = styleSheets[document.uri];
|
||||
if (stylesheetInfo && stylesheetInfo.version === version && stylesheetInfo.languageId === languageId) {
|
||||
stylesheetInfo.cTime = Date.now();
|
||||
return stylesheetInfo.stylesheet;
|
||||
}
|
||||
let stylesheet = parseStylesheet(document);
|
||||
styleSheets[document.uri] = { stylesheet, version, languageId, cTime: Date.now()};
|
||||
if (!stylesheetInfo) {
|
||||
nStyleSheets++;
|
||||
}
|
||||
|
||||
if (nStyleSheets === maxEntries) {
|
||||
let oldestTime = Number.MAX_VALUE;
|
||||
let oldestUri = null;
|
||||
for (let uri in styleSheets) {
|
||||
let stylesheetInfo = styleSheets[uri];
|
||||
if (stylesheetInfo.cTime < oldestTime) {
|
||||
oldestUri = uri;
|
||||
oldestTime = stylesheetInfo.cTime;
|
||||
}
|
||||
}
|
||||
if (oldestUri) {
|
||||
delete styleSheets[oldestUri];
|
||||
nStyleSheets--;
|
||||
}
|
||||
}
|
||||
return stylesheet;
|
||||
|
||||
},
|
||||
onDocumentRemoved(document: TextDocument) {
|
||||
let uri = document.uri;
|
||||
if (styleSheets[uri]) {
|
||||
delete styleSheets[uri];
|
||||
nStyleSheets--;
|
||||
}
|
||||
},
|
||||
dispose() {
|
||||
if (typeof cleanupInterval !== 'undefined') {
|
||||
clearInterval(cleanupInterval);
|
||||
cleanupInterval = void 0;
|
||||
styleSheets = {};
|
||||
nStyleSheets = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
64
extensions/css/server/src/test/css/codeActions.test.ts
Normal file
64
extensions/css/server/src/test/css/codeActions.test.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {CSSCompletion} from '../../services/cssCompletion';
|
||||
import {CSSCodeActions} from '../../services/cssCodeActions';
|
||||
import {CSSValidation} from '../../services/cssValidation';
|
||||
|
||||
import {CompletionList, TextDocument, TextEdit, Position, Range, Command} from 'vscode-languageserver';
|
||||
import {applyEdits} from '../textEditSupport';
|
||||
|
||||
suite('CSS - Code Actions', () => {
|
||||
let testCodeActions = function (value: string, tokenBefore: string): Thenable<{ commands: Command[]; document: TextDocument; }> {
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, value);
|
||||
let styleSheet = new Parser().parseStylesheet(document);
|
||||
let offset = value.indexOf(tokenBefore);
|
||||
let startPosition = document.positionAt(offset);
|
||||
let endPosition = document.positionAt(offset + tokenBefore.length);
|
||||
let range = Range.create(startPosition, endPosition);
|
||||
let validation = new CSSValidation();
|
||||
validation.configure({ validate: true });
|
||||
|
||||
return validation.doValidation(document, styleSheet).then(diagnostics => {
|
||||
return new CSSCodeActions().doCodeActions(document, range, { diagnostics }, styleSheet).then(commands => {
|
||||
return { commands, document };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let assertCodeAction = function (commands: Command[], document: TextDocument, expected: { title: string; content: string; }[]) {
|
||||
let labels = commands.map(command => command.title);
|
||||
|
||||
for (let exp of expected) {
|
||||
let index = labels.indexOf(exp.title);
|
||||
assert.ok(index !== -1, 'Quick fix not found: ' + exp.title + ' , found:' + labels.join(','));
|
||||
let command = commands[index];
|
||||
assert.equal(applyEdits(document, <TextEdit[]>command.arguments[2]), exp.content);
|
||||
assert.equal(command.arguments[0], document.uri);
|
||||
assert.equal(command.arguments[1], document.version);
|
||||
}
|
||||
};
|
||||
|
||||
test('Unknown Properties', function (testDone): any {
|
||||
Promise.all([
|
||||
testCodeActions('body { /*here*/displai: inline }', '/*here*/').then((result) => {
|
||||
assertCodeAction(result.commands, result.document, [
|
||||
{ title: 'Rename to \'display\'', content: 'body { /*here*/display: inline }' }
|
||||
])
|
||||
}),
|
||||
|
||||
testCodeActions('body { /*here*/background-colar: red }', '/*here*/').then((result) => {
|
||||
assertCodeAction(result.commands, result.document, [
|
||||
{ title: 'Rename to \'background-color\'', content: 'body { /*here*/background-color: red }' },
|
||||
{ title: 'Rename to \'background-clip\'', content: 'body { /*here*/background-clip: red }' },
|
||||
{ title: 'Rename to \'background-image\'', content: 'body { /*here*/background-image: red }' }
|
||||
])
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
})
|
230
extensions/css/server/src/test/css/completion.test.ts
Normal file
230
extensions/css/server/src/test/css/completion.test.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {CSSCompletion} from '../../services/cssCompletion';
|
||||
|
||||
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver';
|
||||
import {applyEdits} from '../textEditSupport';
|
||||
|
||||
export interface ItemDescription {
|
||||
label: string;
|
||||
documentation?: string;
|
||||
kind?: CompletionItemKind;
|
||||
resultText?: string;
|
||||
}
|
||||
|
||||
export let assertCompletion = function (completions: CompletionList, expected: ItemDescription, document?: TextDocument) {
|
||||
let matches = completions.items.filter(completion => {
|
||||
return completion.label === expected.label;
|
||||
});
|
||||
assert.equal(matches.length, 1, expected.label + " should only existing once: Actual: " + completions.items.map(c => c.label).join(', '));
|
||||
if (expected.documentation) {
|
||||
assert.equal(matches[0].documentation, expected.documentation);
|
||||
}
|
||||
if (expected.kind) {
|
||||
assert.equal(matches[0].kind, expected.kind);
|
||||
}
|
||||
if (document && expected.resultText) {
|
||||
assert.equal(applyEdits(document, [matches[0].textEdit]), expected.resultText);
|
||||
}
|
||||
};
|
||||
|
||||
suite('CSS - Completion', () => {
|
||||
|
||||
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<void> {
|
||||
let idx = stringBefore ? value.indexOf(stringBefore) + stringBefore.length : 0;
|
||||
|
||||
let completionProvider = new CSSCompletion();
|
||||
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, value);
|
||||
let position = Position.create(0, idx);
|
||||
let jsonDoc = new Parser().parseStylesheet(document);
|
||||
return completionProvider.doComplete(document, position, jsonDoc).then(list => {
|
||||
if (expected.count) {
|
||||
assert.equal(list.items, expected.count);
|
||||
}
|
||||
if (expected.items) {
|
||||
for (let item of expected.items) {
|
||||
assertCompletion(list, item, document);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('sylesheet', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor(' ', null, {
|
||||
items: [
|
||||
{ label: '@import' },
|
||||
{ label: '@keyframes' },
|
||||
{ label: 'div' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor(' body {', null, {
|
||||
items: [
|
||||
{ label: '@import' },
|
||||
{ label: '@keyframes' },
|
||||
{ label: 'html' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('@import url("something.css");', '@', {
|
||||
count: 0
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
test('properties', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body {', '{', {
|
||||
items: [
|
||||
{ label: 'display' },
|
||||
{ label: 'background' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { ver', 'ver', {
|
||||
items: [
|
||||
{ label: 'vertical-align' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align', 'vertical-ali', {
|
||||
items: [
|
||||
{ label: 'vertical-align' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align', 'vertical-align', {
|
||||
items: [
|
||||
{ label: 'vertical-align' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align', {
|
||||
items: [
|
||||
{ label: 'vertical-align' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { trans ', 'trans', {
|
||||
items: [
|
||||
{ label: 'transition' }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
test('values', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align:', {
|
||||
items: [
|
||||
{ label: 'bottom' },
|
||||
{ label: '0cm' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align: ', {
|
||||
items: [
|
||||
{ label: 'bottom' },
|
||||
{ label: '0cm' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bott', 'bott', {
|
||||
items: [
|
||||
{ label: 'bottom' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom }', 'bott', {
|
||||
items: [
|
||||
{ label: 'bottom' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom }', 'bottom', {
|
||||
items: [
|
||||
{ label: 'bottom' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom; }', 'bottom', {
|
||||
items: [
|
||||
{ label: 'bottom' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom; }', 'bottom;', {
|
||||
count: 0
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: bottom; }', 'bottom; ', {
|
||||
items: [
|
||||
{ label: 'display' }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
test('units', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body { vertical-align: 9 }', '9', {
|
||||
items: [
|
||||
{ label: '9cm' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: 1.2 }', '1.2', {
|
||||
items: [
|
||||
{ label: '1.2em' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: 10 }', '1', {
|
||||
items: [
|
||||
{ label: '1cm' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { vertical-align: 10c }', '10c', {
|
||||
items: [
|
||||
{ label: '10cm' }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
test('unknown', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body { notexisting: ;}', 'notexisting: ', {
|
||||
count: 0
|
||||
}),
|
||||
testCompletionFor('.foo { unknown: foo; } .bar { unknown: }', '.bar { unknown:', {
|
||||
items: [
|
||||
{ label: 'foo', kind: CompletionItemKind.Value }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
test('colors', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body { border-right: ', 'right: ', {
|
||||
items: [
|
||||
{ label: 'cyan' },
|
||||
{ label: 'dotted' },
|
||||
{ label: '0em' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { border-right: cyan dotted 2em ', 'cyan', {
|
||||
items: [
|
||||
{ label: 'cyan' },
|
||||
{ label: 'darkcyan' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { border-right: dotted 2em ', '2em ', {
|
||||
items: [
|
||||
{ label: 'cyan' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { background-color: #123456; } .bar { background-color: }', '.bar { background-color:', {
|
||||
items: [
|
||||
{ label: '#123456', kind: CompletionItemKind.Color }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { background-color: r', 'background-color: r', {
|
||||
items: [
|
||||
{ label: 'rgb', kind: CompletionItemKind.Function },
|
||||
{ label: 'rgba', kind: CompletionItemKind.Function },
|
||||
{ label: 'red', kind: CompletionItemKind.Color }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
||||
|
60
extensions/css/server/src/test/css/languageFacts.test.ts
Normal file
60
extensions/css/server/src/test/css/languageFacts.test.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as languageFacts from '../../services/languageFacts';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {TextDocument} from 'vscode-languageserver';
|
||||
|
||||
export function assertColor(parser: Parser, text: string, selection: string, isColor: boolean): void {
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, text);
|
||||
let stylesheet = parser.parseStylesheet(document);
|
||||
assert.equal(0, nodes.ParseErrorCollector.entries(stylesheet).length, 'compile errors');
|
||||
|
||||
let node = nodes.getNodeAtOffset(stylesheet, text.indexOf(selection));
|
||||
|
||||
assert.equal(isColor, languageFacts.isColorValue(node));
|
||||
}
|
||||
|
||||
suite('CSS - Language Facts', () => {
|
||||
|
||||
test('properties', function () {
|
||||
let properties = languageFacts.getProperties();
|
||||
let alignLast = properties['text-align-last'];
|
||||
|
||||
assert.ok(alignLast !== null);
|
||||
assert.equal(alignLast.name, 'text-align-last');
|
||||
let b = alignLast.browsers;
|
||||
assert.equal(b['FF'], '12');
|
||||
assert.equal(b['IE'], '5');
|
||||
assert.equal(b['E'], '');
|
||||
assert.equal(b['C'], void 0);
|
||||
assert.equal(b['count'], 3);
|
||||
|
||||
assert.equal(languageFacts.getBrowserLabel(alignLast.browsers), 'Edge, Firefox 12, IE 5');
|
||||
|
||||
let r = alignLast.restrictions;
|
||||
|
||||
assert.equal(r.length, 1);
|
||||
assert.equal(r[0], 'enum');
|
||||
|
||||
let v = alignLast.values;
|
||||
assert.equal(v.length, 5);
|
||||
assert.equal(v[0].name, 'auto');
|
||||
assert.equal(v[0].browsers.all, true);
|
||||
assert.equal(v[0].browsers.count, Number.MAX_VALUE);
|
||||
});
|
||||
|
||||
test('is color', function () {
|
||||
let parser = new Parser();
|
||||
assertColor(parser, '#main { color: red }', 'red', true);
|
||||
assertColor(parser, '#main { color: #231 }', '#231', true);
|
||||
assertColor(parser, '#main { red: 1 }', 'red', false);
|
||||
assertColor(parser, '#red { foo: 1 }', 'red', false);
|
||||
});
|
||||
});
|
||||
|
129
extensions/css/server/src/test/css/lint.test.ts
Normal file
129
extensions/css/server/src/test/css/lint.test.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {LintVisitor} from '../../services/lint';
|
||||
import {Rule, Rules} from '../../services/lintRules';
|
||||
import {TextDocument} from 'vscode-languageserver';
|
||||
|
||||
export function assertEntries(node: nodes.Node, rules: nodes.IRule[]): void {
|
||||
|
||||
let visitor = new LintVisitor();
|
||||
node.accept(visitor);
|
||||
|
||||
let entries = visitor.getEntries(nodes.Level.Error | nodes.Level.Warning | nodes.Level.Ignore);
|
||||
assert.equal(entries.length, rules.length);
|
||||
|
||||
for (let entry of entries) {
|
||||
let idx = rules.indexOf(entry.getRule());
|
||||
rules.splice(idx, 1);
|
||||
}
|
||||
assert.equal(rules.length, 0);
|
||||
}
|
||||
|
||||
function assertStyleSheet(input: string, ...rules: Rule[]): void {
|
||||
let p = new Parser();
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
|
||||
let node = p.parseStylesheet(document);
|
||||
|
||||
assertEntries(node, rules);
|
||||
}
|
||||
|
||||
function assertRuleSet(input: string, ...rules: Rule[]): void {
|
||||
let p = new Parser();
|
||||
let node = p.internalParse(input, p._parseRuleset);
|
||||
assertEntries(node, rules);
|
||||
}
|
||||
|
||||
|
||||
function assertFontFace(input: string, ...rules: Rule[]): void {
|
||||
let p = new Parser();
|
||||
let node = p.internalParse(input, p._parseFontFace);
|
||||
assertEntries(node, rules);
|
||||
}
|
||||
|
||||
suite('CSS - Lint', () => {
|
||||
|
||||
test('universal selector, empty rule', function () {
|
||||
assertRuleSet('* { color: perty }', Rules.UniversalSelector);
|
||||
assertRuleSet('*, div { color: perty }', Rules.UniversalSelector);
|
||||
assertRuleSet('div, * { color: perty }', Rules.UniversalSelector);
|
||||
assertRuleSet('div > * { color: perty }', Rules.UniversalSelector);
|
||||
assertRuleSet('div + * { color: perty }', Rules.UniversalSelector);
|
||||
});
|
||||
|
||||
test('empty ruleset', function () {
|
||||
assertRuleSet('selector {}', Rules.EmptyRuleSet);
|
||||
});
|
||||
|
||||
test('properies ignored due to inline ', function () {
|
||||
assertRuleSet('selector { display: inline; height: 100px; }', Rules.PropertyIgnoredDueToDisplay);
|
||||
assertRuleSet('selector { display: inline; width: 100px; }', Rules.PropertyIgnoredDueToDisplay);
|
||||
assertRuleSet('selector { display: inline; margin-top: 1em; }', Rules.PropertyIgnoredDueToDisplay);
|
||||
assertRuleSet('selector { display: inline; margin-bottom: 1em; }', Rules.PropertyIgnoredDueToDisplay);
|
||||
assertRuleSet('selector { display: inline; float: right; }', Rules.PropertyIgnoredDueToDisplay, Rules.AvoidFloat);
|
||||
assertRuleSet('selector { display: inline-block; float: right; }', Rules.PropertyIgnoredDueToDisplay, Rules.AvoidFloat);
|
||||
assertRuleSet('selector { display: block; vertical-align: center; }', Rules.PropertyIgnoredDueToDisplay);
|
||||
});
|
||||
|
||||
test('avoid !important', function () {
|
||||
assertRuleSet('selector { display: inline !important; }', Rules.AvoidImportant);
|
||||
});
|
||||
|
||||
test('avoid float', function () {
|
||||
assertRuleSet('selector { float: right; }', Rules.AvoidFloat);
|
||||
});
|
||||
|
||||
test('avoid id selectors', function () {
|
||||
assertRuleSet('#selector { display: inline; }', Rules.AvoidIdSelector);
|
||||
});
|
||||
|
||||
test('zero with unit', function () {
|
||||
// assertRuleSet('selector { width: 0px }', lint.Rules.ZeroWithUnit);
|
||||
assertRuleSet('selector { width: 0% }');
|
||||
});
|
||||
|
||||
test('duplicate declarations', function () {
|
||||
assertRuleSet('selector { color: perty; color: perty }', Rules.DuplicateDeclarations, Rules.DuplicateDeclarations);
|
||||
assertRuleSet('selector { color: -o-perty; color: perty }');
|
||||
});
|
||||
|
||||
test('unknown properties', function () {
|
||||
assertRuleSet('selector { -ms-property: "rest is missing" }', Rules.UnknownVendorSpecificProperty);
|
||||
assertRuleSet('selector { -moz-box-shadow: "rest is missing" }', Rules.UnknownVendorSpecificProperty, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
|
||||
assertRuleSet('selector { box-shadow: none }'); // no error
|
||||
assertRuleSet('selector { box-property: "rest is missing" }', Rules.UnknownProperty);
|
||||
|
||||
});
|
||||
|
||||
test('IE hacks', function () {
|
||||
assertRuleSet('selector { display: inline-block; *display: inline; }', Rules.IEStarHack);
|
||||
assertRuleSet('selector { background: #00f; /* all browsers including Mac IE */ *background: #f00; /* IE 7 and below */ _background: #f60; /* IE 6 and below */ }', Rules.IEStarHack, Rules.IEStarHack);
|
||||
});
|
||||
|
||||
test('vendor specific prefixes', function () {
|
||||
assertRuleSet('selector { -moz-animation: none }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
|
||||
assertRuleSet('selector { -moz-transform: none; transform: none }', Rules.AllVendorPrefixes);
|
||||
assertRuleSet('selector { transform: none; }');
|
||||
assertRuleSet('selector { -moz-transform: none; transform: none; -o-transform: none; -webkit-transform: none; -ms-transform: none; }');
|
||||
assertRuleSet('selector { --transform: none; }');
|
||||
});
|
||||
|
||||
test('font-face required properties', function () {
|
||||
assertFontFace('@font-face { }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { src: url(test.tff) }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { font-family: \'name\' }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { src: url(test.tff); font-family: \'name\' }'); // no error
|
||||
});
|
||||
|
||||
test('keyframes', function () {
|
||||
assertStyleSheet('@keyframes foo { }');
|
||||
assertStyleSheet('@keyframes foo { } @-moz-keyframes foo { }', Rules.AllVendorPrefixes);
|
||||
assertStyleSheet('@-moz-keyframes foo { }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
|
||||
});
|
||||
});
|
242
extensions/css/server/src/test/css/navigation.test.ts
Normal file
242
extensions/css/server/src/test/css/navigation.test.ts
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Scope, GlobalScope, ScopeBuilder} from '../../parser/cssSymbolScope';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {CSSNavigation} from '../../services/cssNavigation';
|
||||
|
||||
import {TextDocument, DocumentHighlightKind} from 'vscode-languageserver';
|
||||
|
||||
export function assertScopesAndSymbols(p: Parser, input: string, expected: string): void {
|
||||
let global = createScope(p, input);
|
||||
assert.equal(scopeToString(global), expected);
|
||||
}
|
||||
|
||||
export function assertHighlights(p: Parser, input: string, marker: string, expectedMatches: number, expectedWrites: number, elementName?: string): Thenable<void> {
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
|
||||
|
||||
let stylesheet = p.parseStylesheet(document);
|
||||
assertNoErrors(stylesheet);
|
||||
|
||||
let index = input.indexOf(marker) + marker.length;
|
||||
let position = document.positionAt(index);
|
||||
|
||||
return new CSSNavigation().findDocumentHighlights(document, position, stylesheet).then(highlights => {
|
||||
assert.equal(highlights.length, expectedMatches, input);
|
||||
|
||||
let nWrites = 0;
|
||||
for (let highlight of highlights) {
|
||||
if (highlight.kind === DocumentHighlightKind.Write) {
|
||||
nWrites++;
|
||||
}
|
||||
let range = highlight.range;
|
||||
let start = document.offsetAt(range.start), end = document.offsetAt(range.end);
|
||||
assert.equal(document.getText().substring(start, end), elementName || marker);
|
||||
}
|
||||
assert.equal(nWrites, expectedWrites);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function assertSymbolsInScope(p: Parser, input: string, offset: number, ...selections: { name: string; type: nodes.ReferenceType }[]): void {
|
||||
|
||||
let global = createScope(p, input);
|
||||
|
||||
let scope = global.findScope(offset);
|
||||
|
||||
let getErrorMessage = function (name: string) {
|
||||
let all = 'symbol ' + name + ' not found. In scope: ';
|
||||
scope.getSymbols().forEach((sym) => { all += (sym.name + ' '); });
|
||||
return all;
|
||||
};
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
let selection = selections[i];
|
||||
let sym = scope.getSymbol(selection.name, selection.type) || global.getSymbol(selection.name, selection.type);
|
||||
assert.ok(!!sym, getErrorMessage(selection.name));
|
||||
}
|
||||
}
|
||||
|
||||
export function assertScopeBuilding(p: Parser, input: string, ...scopes: { offset: number; length: number; }[]): void {
|
||||
|
||||
let global = createScope(p, input);
|
||||
|
||||
function assertChildren(scope: Scope): void {
|
||||
|
||||
scope.children.forEach((scope) => {
|
||||
|
||||
// check bounds
|
||||
let expected = scopes.shift();
|
||||
assert.equal(scope.offset, expected.offset);
|
||||
assert.equal(scope.length, expected.length);
|
||||
|
||||
// recursive descent
|
||||
assertChildren(scope);
|
||||
});
|
||||
}
|
||||
|
||||
assertChildren(global);
|
||||
|
||||
assert.equal(scopes.length, 0, 'remainig scopes: ' + scopes.join());
|
||||
}
|
||||
|
||||
function scopeToString(scope: Scope): string {
|
||||
let str = '';
|
||||
let symbols = scope.getSymbols();
|
||||
for (let index = 0; index < symbols.length; index++) {
|
||||
if (str.length > 0) {
|
||||
str += ',';
|
||||
}
|
||||
str += symbols[index].name;
|
||||
}
|
||||
let scopes = scope.children;
|
||||
for (let index = 0; index < scopes.length; index++) {
|
||||
if (str.length > 0) {
|
||||
str += ',';
|
||||
}
|
||||
str += ('[' + scopeToString(scopes[index]) + ']');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function assertNoErrors(node: nodes.Node): void {
|
||||
let markers = nodes.ParseErrorCollector.entries(node);
|
||||
if (markers.length > 0) {
|
||||
assert.ok(false, 'node has errors: ' + markers[0].getMessage() + ', offset: ' + markers[0].getNode().offset);
|
||||
}
|
||||
}
|
||||
|
||||
function createScope(p: Parser, input: string): Scope {
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
|
||||
|
||||
let styleSheet = p.parseStylesheet(document),
|
||||
global = new GlobalScope(),
|
||||
builder = new ScopeBuilder(global);
|
||||
|
||||
assertNoErrors(styleSheet);
|
||||
styleSheet.accept(builder);
|
||||
return global;
|
||||
}
|
||||
|
||||
suite('CSS - Symbols', () => {
|
||||
|
||||
test('scope creation', function () {
|
||||
|
||||
let global = new GlobalScope(),
|
||||
child1 = new Scope(10, 5),
|
||||
child2 = new Scope(15, 5);
|
||||
|
||||
global.addChild(child1);
|
||||
global.addChild(child2);
|
||||
|
||||
assert.equal(global.children.length, 2);
|
||||
assert.ok(child1.parent === global);
|
||||
assert.ok(child2.parent === global);
|
||||
|
||||
// find children
|
||||
assert.ok(global.findScope(-1) === null);
|
||||
assert.ok(global.findScope(0) === global);
|
||||
assert.ok(global.findScope(10) === child1);
|
||||
assert.ok(global.findScope(14) === child1);
|
||||
assert.ok(global.findScope(15) === child2);
|
||||
assert.ok(global.findScope(19) === child2);
|
||||
assert.ok(global.findScope(19).parent === global);
|
||||
});
|
||||
|
||||
test('scope building', function () {
|
||||
let p = new Parser();
|
||||
assertScopeBuilding(p, '.class {}', { offset: 7, length: 2 });
|
||||
assertScopeBuilding(p, '.class {} .class {}', { offset: 7, length: 2 }, { offset: 17, length: 2 });
|
||||
});
|
||||
|
||||
test('symbols in scopes', function () {
|
||||
let p = new Parser();
|
||||
assertSymbolsInScope(p, '@keyframes animation {};', 0, { name: 'animation', type: nodes.ReferenceType.Keyframe });
|
||||
assertSymbolsInScope(p, ' .class1 {} .class2 {}', 0, { name: '.class1', type: nodes.ReferenceType.Rule }, { name: '.class2', type: nodes.ReferenceType.Rule });
|
||||
});
|
||||
|
||||
test('scopes and symbols', function () {
|
||||
let p = new Parser();
|
||||
assertScopesAndSymbols(p, '.class {}', '.class,[]');
|
||||
assertScopesAndSymbols(p, '@keyframes animation {}; .class {}', 'animation,.class,[],[]');
|
||||
assertScopesAndSymbols(p, '@page :pseudo-class { margin:2in; }', '[]');
|
||||
assertScopesAndSymbols(p, '@media print { body { font-size: 10pt } }', '[body,[]]');
|
||||
assertScopesAndSymbols(p, '@-moz-keyframes identifier { 0% { top: 0; } 50% { top: 30px; left: 20px; }}', 'identifier,[[],[]]');
|
||||
assertScopesAndSymbols(p, '@font-face { font-family: "Bitstream Vera Serif Bold"; }', '[]');
|
||||
});
|
||||
|
||||
test('mark highlights', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '@keyframes id {}; #main { animation: id 4s linear 0s infinite alternate; }', 'id', 2, 1),
|
||||
assertHighlights(p, '@keyframes id {}; #main { animation-name: id; foo: id;}', 'id', 2, 1)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('test variables in root scope', function () {
|
||||
let p = new Parser();
|
||||
assertSymbolsInScope(p, ':root{ --var1: abc; --var2: def; }', 0, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
|
||||
});
|
||||
|
||||
test('test variables in local scope', function () {
|
||||
let p = new Parser();
|
||||
assertSymbolsInScope(p, '.a{ --var1: abc; --var2: def; }', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
|
||||
});
|
||||
|
||||
test('test variables in local scope get root variables too', function () {
|
||||
let p = new Parser();
|
||||
assertSymbolsInScope(p, '.a{ --var1: abc; } :root{ --var2: abc;}', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
|
||||
});
|
||||
|
||||
test('test variables in local scope get root variables and other local variables too', function () {
|
||||
let p = new Parser();
|
||||
assertSymbolsInScope(p, '.a{ --var1: abc; } .b{ --var2: abc; } :root{ --var3: abc;}', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable }, { name: '--var3', type: nodes.ReferenceType.Variable });
|
||||
});
|
||||
|
||||
test('mark occurrences for variable defined in root and used in a rule', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '.a{ background: let(--var1); } :root{ --var1: abc;}', '--var1', 2, 1)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('mark occurrences for variable defined in a rule and used in a different rule', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '.a{ background: let(--var1); } :b{ --var1: abc;}', '--var1', 2, 1)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('mark occurrences for property', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'display', 2, 0)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('mark occurrences for value', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'inline', 2, 0)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('mark occurrences for selector', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'body', 1, 1)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('mark occurrences for comment', function (testDone) {
|
||||
let p = new Parser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '/* comment */body { display: inline } ', 'comment', 0, 0)
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
118
extensions/css/server/src/test/css/nodes.test.ts
Normal file
118
extensions/css/server/src/test/css/nodes.test.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
|
||||
export class PrintingVisitor implements nodes.IVisitor {
|
||||
|
||||
public tree: string[] = [];
|
||||
|
||||
public visitNode(node: nodes.Node): boolean {
|
||||
this.tree.push(nodes.NodeType[node.type].toLowerCase());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNodes(fn: (input: string) => nodes.Node, input: string, expected: string): void {
|
||||
let node = fn(input);
|
||||
let visitor = new PrintingVisitor();
|
||||
|
||||
node.accept(visitor);
|
||||
|
||||
let actual = visitor.tree.join(',') + ',';
|
||||
let segments = expected.split(',');
|
||||
let oldIndex: number = undefined;
|
||||
let index = -1;
|
||||
|
||||
while (segments.length > 0) {
|
||||
let segment = segments.shift();
|
||||
if (segment === '...') {
|
||||
continue;
|
||||
}
|
||||
index = actual.indexOf(segment + ',', oldIndex);
|
||||
if (index <= oldIndex) {
|
||||
assert.ok(false, segment + ' NOT found in ' + actual);
|
||||
}
|
||||
oldIndex = index + segment.length;
|
||||
}
|
||||
|
||||
assert.ok(true);
|
||||
}
|
||||
|
||||
suite('CSS - Nodes', () => {
|
||||
|
||||
test('Test Node', function () {
|
||||
|
||||
let node = new nodes.Node();
|
||||
assert.equal(node.offset, -1);
|
||||
assert.equal(node.length, -1);
|
||||
assert.equal(node.parent, null);
|
||||
assert.equal(node.getChildren().length, 0);
|
||||
|
||||
let c = 0;
|
||||
node.accept((n: nodes.Node) => {
|
||||
assert.ok(n === node);
|
||||
c += 1;
|
||||
return true;
|
||||
});
|
||||
assert.equal(c, 1);
|
||||
|
||||
let child = new nodes.Node();
|
||||
node.adoptChild(child);
|
||||
|
||||
c = 0;
|
||||
let expects = [node, child];
|
||||
node.accept((n: nodes.Node) => {
|
||||
assert.ok(n === expects[c]);
|
||||
c += 1;
|
||||
return true;
|
||||
});
|
||||
assert.equal(c, 2);
|
||||
});
|
||||
|
||||
test('Test Adopting', function () {
|
||||
|
||||
let child = new nodes.Node();
|
||||
let p1 = new nodes.Node();
|
||||
let p2 = new nodes.Node();
|
||||
|
||||
assert.ok(child.parent === null);
|
||||
assert.equal(p1.getChildren().length, 0);
|
||||
assert.equal(p2.getChildren().length, 0);
|
||||
|
||||
p1.adoptChild(child);
|
||||
assert.ok(child.parent === p1);
|
||||
assert.equal(p1.getChildren().length, 1);
|
||||
assert.equal(p2.getChildren().length, 0);
|
||||
|
||||
p2.adoptChild(child);
|
||||
assert.ok(child.parent === p2);
|
||||
assert.equal(p1.getChildren().length, 0);
|
||||
assert.equal(p2.getChildren().length, 1);
|
||||
});
|
||||
|
||||
function ruleset(input: string): nodes.RuleSet {
|
||||
let parser = new Parser();
|
||||
let node = parser.internalParse(input, parser._parseRuleset);
|
||||
return node;
|
||||
}
|
||||
|
||||
test('RuleSet', function () {
|
||||
assertNodes(ruleset, 'selector { prop: value }', 'ruleset,...,selector,...,declaration,...,property,...,expression');
|
||||
assertNodes(ruleset, 'selector { prop; }', 'ruleset,...,selector,...,selector');
|
||||
});
|
||||
|
||||
test('Keyframe', function () {
|
||||
function fn(input: string): nodes.Node {
|
||||
let parser = new Parser();
|
||||
let node = parser.internalParse(input, parser._parseKeyframe);
|
||||
return node;
|
||||
};
|
||||
assertNodes(fn, '@keyframes name { from { top: 0px} to { top: 100px } }', 'keyframe,identifier,keyframeselector,declaration,keyframeselector,declaration');
|
||||
});
|
||||
});
|
404
extensions/css/server/src/test/css/parser.test.ts
Normal file
404
extensions/css/server/src/test/css/parser.test.ts
Normal file
|
@ -0,0 +1,404 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {TokenType} from '../../parser/cssScanner';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {ParseError} from '../../parser/cssErrors';
|
||||
|
||||
export function assertNode(text: string, parser: Parser, f: () => nodes.Node): nodes.Node {
|
||||
let node = parser.internalParse(text, f);
|
||||
assert.ok(node !== null, 'no node returned');
|
||||
let markers = nodes.ParseErrorCollector.entries(node);
|
||||
if (markers.length > 0) {
|
||||
assert.ok(false, 'node has errors: ' + markers[0].getMessage() + ', offset: ' + markers[0].getNode().offset);
|
||||
}
|
||||
assert.ok(parser.accept(TokenType.EOF), 'Expect scanner at EOF');
|
||||
return node;
|
||||
}
|
||||
|
||||
export function assertFunction(text: string, parser: Parser, f: () => nodes.Node): void {
|
||||
assertNode(text, parser, f);
|
||||
}
|
||||
|
||||
export function assertNoNode(text: string, parser: Parser, f: () => nodes.Node): void {
|
||||
let node = parser.internalParse(text, f);
|
||||
assert.ok(node === null);
|
||||
}
|
||||
|
||||
export function assertError(text: string, parser: Parser, f: () => nodes.Node, error: nodes.IRule): void {
|
||||
let node = parser.internalParse(text, f);
|
||||
assert.ok(node !== null, 'no node returned');
|
||||
let markers = nodes.ParseErrorCollector.entries(node);
|
||||
if (markers.length === 0) {
|
||||
assert.ok(false, 'no errors but error expected: ' + error.message);
|
||||
} else {
|
||||
markers = markers.sort((a, b) => { return a.getOffset() - b.getOffset(); });
|
||||
assert.equal(markers[0].getRule().id, error.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suite('CSS - Parser', () => {
|
||||
|
||||
test('Test stylesheet', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@charset "demo" ;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('body { margin: 0px; padding: 3em, 6em; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('--> <!--', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('<!-- --> @import "string"; <!-- -->', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media asdsa { } <!-- --> <!-- -->', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media screen, projection { }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media screen and (max-width: 400px) { @-ms-viewport { width: 320px; }}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@-ms-viewport { width: 320px; height: 768px; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('#boo, far {} \n.far boo {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@-moz-keyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@import "foo";', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@import url(/css/screen.css) screen, projection;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@page { margin: 2.5cm; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@font-face { font-family: "Example Font"; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@namespace "http://www.w3.org/1999/xhtml";', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@namespace pref url(http://test);', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) { body { color: purple; background: yellow; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('input[type=\"submit\"] {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('E::first-line E::first-letter E::before E::after {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('E.warning E#myid E:not(s) {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('@namespace;', parser, parser._parseStylesheet.bind(parser), ParseError.URIExpected);
|
||||
assertError('@namespace url(http://test)', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
|
||||
assertError('@mskeyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser), ParseError.UnknownAtRule);
|
||||
assertError('@charset;', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@charset \'utf8\'', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
|
||||
});
|
||||
|
||||
test('Stylesheet /Panic/', function () {
|
||||
let parser = new Parser();
|
||||
assertError('#boo, far } \n.far boo {}', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('#boo, far { far: 43px; \n.far boo {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
|
||||
});
|
||||
|
||||
test('@font-face', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@font-face {}', parser, parser._parseFontFace.bind(parser));
|
||||
assertNode('@font-face { src: url(http://test) }', parser, parser._parseFontFace.bind(parser));
|
||||
assertNode('@font-face { font-style: normal; font-stretch: normal; }', parser, parser._parseFontFace.bind(parser));
|
||||
assertError('@font-face { font-style: normal font-stretch: normal; }', parser, parser._parseFontFace.bind(parser), ParseError.SemiColonExpected);
|
||||
});
|
||||
|
||||
test('@keyframe selector', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('from {}', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('to {}', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('0% {}', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('10% {}', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('100000% {}', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('from { width: 100% }', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
assertNode('from { width: 100%; to: 10px; }', parser, parser._parseKeyframeSelector.bind(parser));
|
||||
});
|
||||
|
||||
test('@keyframe', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@keyframes name {}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@-webkit-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@-o-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@-moz-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@keyframes name { from {} to {}}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@keyframes name { from {} 80% {} 100% {}}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@keyframes name { from { top: 0px; } 80% { top: 100px; } 100% { top: 50px; }}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@keyframes name { from { top: 0px; } 70%, 80% { top: 100px; } 100% { top: 50px; }}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertNode('@keyframes name { from { top: 0px; left: 1px; right: 2px }}', parser, parser._parseKeyframe.bind(parser));
|
||||
assertError('@keyframes name { from { top: 0px; left: 1px, right: 2px }}', parser, parser._parseKeyframe.bind(parser), ParseError.SemiColonExpected);
|
||||
assertError('@keyframes )', parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@keyframes name { { top: 0px; } }', parser, parser._parseKeyframe.bind(parser), ParseError.RightCurlyExpected);
|
||||
assertError('@keyframes name { from, #123', parser, parser._parseKeyframe.bind(parser), ParseError.PercentageExpected);
|
||||
});
|
||||
|
||||
test('Test import', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@import "asdasdsa"', parser, parser._parseImport.bind(parser));
|
||||
assertNode('@ImPort "asdsadsa"', parser, parser._parseImport.bind(parser));
|
||||
assertNode('@import "asdasd" dsfsdf', parser, parser._parseImport.bind(parser));
|
||||
assertError('@import', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
|
||||
});
|
||||
|
||||
test('Test media', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@media asdsa { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@meDia sadd{} ', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media somename, othername2 { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media only screen and (max-width:850px) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media only screen and (max-width:850px) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media all and (min-width:500px) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media screen and (color), projection and (color) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media not screen and (device-aspect-ratio: 16/9) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media print and (min-resolution: 300dpi) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media print and (min-resolution: 118dpcm) { }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }', parser, parser._parseMedia.bind(parser));
|
||||
assertNode('@media print { body:before { } }', parser, parser._parseMedia.bind(parser));
|
||||
assertError('@media somename othername2 { }', parser, parser._parseMedia.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@media not, screen { }', parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@media not screen and foo { }', parser, parser._parseMedia.bind(parser), ParseError.LeftParenthesisExpected);
|
||||
assertError('@media not screen and () { }', parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@media not screen and (color:) { }', parser, parser._parseMedia.bind(parser), ParseError.TermExpected);
|
||||
assertError('@media not screen and (color:#234567 { }', parser, parser._parseMedia.bind(parser), ParseError.RightParenthesisExpected);
|
||||
});
|
||||
|
||||
test('Test media_list', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('somename', parser, parser._parseMediaList.bind(parser));
|
||||
assertNode('somename, othername', parser, parser._parseMediaList.bind(parser));
|
||||
});
|
||||
|
||||
test('medium', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('somename', parser, parser._parseMedium.bind(parser));
|
||||
assertNode('-asdas', parser, parser._parseMedium.bind(parser));
|
||||
assertNode('-asda34s', parser, parser._parseMedium.bind(parser));
|
||||
});
|
||||
|
||||
test('page', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('@page : name{ }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page :left, :right { }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page : name{ some : "asdas" }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page : name{ some : "asdas" !important }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page : name{ some : "asdas" !important; some : "asdas" !important }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page rotated { size : landscape }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page :left { margin-left: 4cm; margin-right: 3cm; }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page { @top-right-corner { content: url(foo.png); border: solid green; } }', parser, parser._parsePage.bind(parser));
|
||||
assertNode('@page { @top-left-corner { content: " "; border: solid green; } @bottom-right-corner { content: counter(page); border: solid green; } }', parser, parser._parsePage.bind(parser));
|
||||
assertError('@page { @top-left-corner foo { content: " "; border: solid green; } }', parser, parser._parsePage.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@page { @XY foo { content: " "; border: solid green; } }', parser, parser._parsePage.bind(parser), ParseError.UnknownAtRule);
|
||||
assertError('@page :left { margin-left: 4cm margin-right: 3cm; }', parser, parser._parsePage.bind(parser), ParseError.SemiColonExpected);
|
||||
assertError('@page : { }', parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@page :left, { }', parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected);
|
||||
});
|
||||
|
||||
test('pseudo page', function () {
|
||||
let parser = new Parser();
|
||||
assertNode(': some ', parser, parser._parsePageSelector.bind(parser));
|
||||
});
|
||||
|
||||
test('operator', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('/', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('*', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('+', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('-', parser, parser._parseOperator.bind(parser));
|
||||
});
|
||||
|
||||
test('combinator', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('+', parser, parser._parseCombinator.bind(parser));
|
||||
assertNode('+ ', parser, parser._parseCombinator.bind(parser));
|
||||
assertNode('> ', parser, parser._parseCombinator.bind(parser));
|
||||
assertNode('>', parser, parser._parseCombinator.bind(parser));
|
||||
});
|
||||
|
||||
test('unary_operator', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('-', parser, parser._parseUnaryOperator.bind(parser));
|
||||
assertNode('+', parser, parser._parseUnaryOperator.bind(parser));
|
||||
});
|
||||
|
||||
test('Property', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('asdsa', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('asdsa334', parser, parser._parseProperty.bind(parser));
|
||||
|
||||
assertNode('--color', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('--primary-font', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('-color', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('somevar', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('some--let', parser, parser._parseProperty.bind(parser));
|
||||
assertNode('somevar--', parser, parser._parseProperty.bind(parser));
|
||||
});
|
||||
|
||||
test('Ruleset', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('name{ }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode(' name\n{ some : "asdas" }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode(' name{ some : "asdas" !important }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('name{ \n some : "asdas" !important; some : "asdas" }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('* {}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('.far{}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('boo {}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('.far #boo {}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('boo { prop: value }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('boo { prop: value; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('boo { prop: value; prop: value }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('boo { prop: value; prop: value; }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Ruleset /Panic/', function () {
|
||||
let parser = new Parser();
|
||||
// assertNode('boo { : value }', parser, parser._parseRuleset.bind(parser));
|
||||
assertError('boo { prop: ; }', parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected);
|
||||
assertError('boo { prop }', parser, parser._parseRuleset.bind(parser), ParseError.ColonExpected);
|
||||
assertError('boo { prop: ; far: 12em; }', parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected);
|
||||
// assertNode('boo { prop: ; 1ar: 12em; }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('selector', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('asdsa', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('asdsa + asdas', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('asdsa + asdas + name', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('asdsa + asdas + name', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('name #id#anotherid', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('name.far .boo', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('name .name .zweitername', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('*', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('#id', parser, parser._parseSelector.bind(parser));
|
||||
assertNode('far.boo', parser, parser._parseSelector.bind(parser));
|
||||
});
|
||||
|
||||
test('simple selector', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('name', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('#id#anotherid', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('name.far', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('name.erstername.zweitername', parser, parser._parseSimpleSelector.bind(parser));
|
||||
});
|
||||
|
||||
test('element name', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('name', parser, parser._parseElementName.bind(parser));
|
||||
assertNode('*', parser, parser._parseElementName.bind(parser));
|
||||
});
|
||||
|
||||
test('attrib', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('[name]', parser, parser._parseAttrib.bind(parser));
|
||||
assertNode('[name = name2]', parser, parser._parseAttrib.bind(parser));
|
||||
assertNode('[name ~= name3]', parser, parser._parseAttrib.bind(parser));
|
||||
assertNode('[name~=name3]', parser, parser._parseAttrib.bind(parser));
|
||||
assertNode('[name |= name3]', parser, parser._parseAttrib.bind(parser));
|
||||
assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser));
|
||||
});
|
||||
|
||||
test('pseudo', function () {
|
||||
let parser = new Parser();
|
||||
assertNode(':some', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':some(thing)', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':nth-child(12)', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':lang(it)', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':not(.class)', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':not(:disabled)', parser, parser._parsePseudo.bind(parser));
|
||||
assertNode(':not(#foo)', parser, parser._parsePseudo.bind(parser));
|
||||
});
|
||||
|
||||
test('declaration', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('property:12', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('-vendor-property: 12', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('font-size: 12px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color : #888 /4', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('filter : progid: DXImageTransform.\nMicrosoft.\nDropShadow(\noffx=2, offy=1, color=#000000)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('font-size: 12px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('*background: #f00 /* IE 7 and below */', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('_background: #f60 /* IE 6 and below */', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)', parser, parser._parseDeclaration.bind(parser));
|
||||
|
||||
assertNode('--color: #F5F5F5', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--color: 0', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--color: 255', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--color: 25.5', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--color: 25px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--color: 25.5px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('--primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseDeclaration.bind(parser));
|
||||
assertError('--color : ', parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected);
|
||||
assertError('--color value', parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected);
|
||||
});
|
||||
|
||||
|
||||
test('term', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('"asdasd"', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('name', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('#FFFFFF', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('url("this is a url")', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('+324', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('-45', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('+45', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('-45%', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('-45mm', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('-45em', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('"asdsa"', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('faa', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('#FFFFFF', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('name(asd)', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('calc(50% + 20px)', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('calc(50% + (100%/3 - 2*1em - 2*1px))', parser, parser._parseTerm.bind(parser));
|
||||
assertNoNode('%(\'repetitions: %S file: %S\', 1 + 2, "directory/file.less")', parser, parser._parseTerm.bind(parser)); // less syntax
|
||||
assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax
|
||||
});
|
||||
|
||||
test('function', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('name( "bla" )', parser, parser._parseFunction.bind(parser));
|
||||
assertNode('name( name )', parser, parser._parseFunction.bind(parser));
|
||||
assertNode('name( -500mm )', parser, parser._parseFunction.bind(parser));
|
||||
assertNode('\u060frf()', parser, parser._parseFunction.bind(parser));
|
||||
assertNode('über()', parser, parser._parseFunction.bind(parser));
|
||||
|
||||
assertNoNode('über ()', parser, parser._parseFunction.bind(parser));
|
||||
assertNoNode('%()', parser, parser._parseFunction.bind(parser));
|
||||
assertNoNode('% ()', parser, parser._parseFunction.bind(parser));
|
||||
|
||||
assertFunction('let(--color)', parser, parser._parseFunction.bind(parser));
|
||||
assertFunction('let(--color, somevalue)', parser, parser._parseFunction.bind(parser));
|
||||
assertFunction('let(--variable1, --variable2)', parser, parser._parseFunction.bind(parser));
|
||||
assertFunction('let(--variable1, let(--variable2))', parser, parser._parseFunction.bind(parser));
|
||||
assertFunction('fun(value1, value2)', parser, parser._parseFunction.bind(parser));
|
||||
});
|
||||
|
||||
test('Test Token prio', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('!important', parser, parser._parsePrio.bind(parser));
|
||||
assertNode('!/*demo*/important', parser, parser._parsePrio.bind(parser));
|
||||
assertNode('! /*demo*/ important', parser, parser._parsePrio.bind(parser));
|
||||
assertNode('! /*dem o*/ important', parser, parser._parsePrio.bind(parser));
|
||||
});
|
||||
|
||||
test('hexcolor', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('#FFF', parser, parser._parseHexColor.bind(parser));
|
||||
assertNode('#FFFFFF', parser, parser._parseHexColor.bind(parser));
|
||||
});
|
||||
|
||||
test('Test class', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('.faa', parser, parser._parseClass.bind(parser));
|
||||
assertNode('faa', parser, parser._parseElementName.bind(parser));
|
||||
assertNode('*', parser, parser._parseElementName.bind(parser));
|
||||
assertNode('.faa42', parser, parser._parseClass.bind(parser));
|
||||
});
|
||||
|
||||
|
||||
test('Prio', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('!important', parser, parser._parsePrio.bind(parser));
|
||||
});
|
||||
|
||||
test('Expr', function () {
|
||||
let parser = new Parser();
|
||||
assertNode('45,5px', parser, parser._parseExpr.bind(parser));
|
||||
assertNode(' 45 , 5px ', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('5/6', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('36mm, -webkit-calc(100%-10px)', parser, parser._parseExpr.bind(parser));
|
||||
});
|
||||
|
||||
});
|
||||
|
228
extensions/css/server/src/test/css/scanner.test.ts
Normal file
228
extensions/css/server/src/test/css/scanner.test.ts
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Scanner, TokenType} from '../../parser/cssScanner';
|
||||
|
||||
suite('CSS - Scanner', () => {
|
||||
|
||||
function assertSingleToken(scan: Scanner, source: string, len: number, offset: number, text: string, type: TokenType): void {
|
||||
scan.setSource(source);
|
||||
let token = scan.scan();
|
||||
assert.equal(token.len, len);
|
||||
assert.equal(token.offset, offset);
|
||||
assert.equal(token.text, text);
|
||||
assert.equal(token.type, type);
|
||||
}
|
||||
|
||||
test('Whitespace', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, ' @', 1, 1, '@', TokenType.Delim);
|
||||
assertSingleToken(scanner, ' /* comment*/ \n/*comment*/@', 1, 26, '@', TokenType.Delim);
|
||||
|
||||
scanner = new Scanner();
|
||||
scanner.ignoreWhitespace = false;
|
||||
assertSingleToken(scanner, ' @', 1, 0, ' ', TokenType.Whitespace);
|
||||
assertSingleToken(scanner, '/*comment*/ @', 1, 11, ' ', TokenType.Whitespace);
|
||||
|
||||
scanner = new Scanner();
|
||||
scanner.ignoreComment = false;
|
||||
assertSingleToken(scanner, ' /*comment*/@', 11, 1, '/*comment*/', TokenType.Comment);
|
||||
assertSingleToken(scanner, '/*comment*/ @', 11, 0, '/*comment*/', TokenType.Comment);
|
||||
});
|
||||
|
||||
test('Token Ident', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '\u060frf', 3, 0, '\u060frf', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'über', 4, 0, 'über', TokenType.Ident);
|
||||
assertSingleToken(scanner, '-bo', 3, 0, '-bo', TokenType.Ident);
|
||||
assertSingleToken(scanner, '_bo', 3, 0, '_bo', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'boo', 3, 0, 'boo', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'Boo', 3, 0, 'Boo', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'red--', 5, 0, 'red--', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'red-->', 5, 0, 'red--', TokenType.Ident);
|
||||
assertSingleToken(scanner, '--red', 5, 0, '--red', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'a\\.b', 4, 0, 'a\.b', TokenType.Ident);
|
||||
assertSingleToken(scanner, '\\E9motion', 9, 0, 'émotion', TokenType.Ident);
|
||||
assertSingleToken(scanner, '\\E9 dition', 10, 0, 'édition', TokenType.Ident);
|
||||
assertSingleToken(scanner, '\\0000E9dition', 13, 0, 'édition', TokenType.Ident);
|
||||
assertSingleToken(scanner, 'S\\0000e9f', 9, 0, 'Séf', TokenType.Ident);
|
||||
});
|
||||
|
||||
test('Token Url', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, 'url(\'http://msft.com\')', 22, 0, 'url(\'http://msft.com\')', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url("http://msft.com")', 22, 0, 'url("http://msft.com")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url( "http://msft.com")', 23, 0, 'url( "http://msft.com")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url(\t"http://msft.com")', 23, 0, 'url(\t"http://msft.com")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url(\n"http://msft.com")', 23, 0, 'url(\n"http://msft.com")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url("http://msft.com"\n)', 23, 0, 'url("http://msft.com"\n)', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url("")', 7, 0, 'url("")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'uRL("")', 7, 0, 'uRL("")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'URL("")', 7, 0, 'URL("")', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url(http://msft.com)', 20, 0, 'url(http://msft.com)', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url()', 5, 0, 'url()', TokenType.URI);
|
||||
assertSingleToken(scanner, 'url(\'http://msft.com\n)', 22, 0, 'url(\'http://msft.com\n)', TokenType.BadUri);
|
||||
assertSingleToken(scanner, 'url("http://msft.com"', 21, 0, 'url("http://msft.com"', TokenType.BadUri);
|
||||
assertSingleToken(scanner, 'url(http://msft.com\')', 21, 0, 'url(http://msft.com\')', TokenType.URI);
|
||||
});
|
||||
|
||||
test('Token AtKeyword', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '@import', 7, 0, '@import', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@importttt', 10, 0, '@importttt', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@imp', 4, 0, '@imp', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@5', 2, 0, '@5', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@media', 6, 0, '@media', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@page', 5, 0, '@page', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@charset', 8, 0, '@charset', TokenType.Charset);
|
||||
assertSingleToken(scanner, '@-mport', 7, 0, '@-mport', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@\u00f0mport', 7, 0, '@\u00f0mport', TokenType.AtKeyword);
|
||||
assertSingleToken(scanner, '@', 1, 0, '@', TokenType.Delim);
|
||||
});
|
||||
|
||||
test('Token Number', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '1234', 4, 0, '1234', TokenType.Num);
|
||||
assertSingleToken(scanner, '1.34', 4, 0, '1.34', TokenType.Num);
|
||||
assertSingleToken(scanner, '.234', 4, 0, '.234', TokenType.Num);
|
||||
assertSingleToken(scanner, '.234.', 4, 0, '.234', TokenType.Num);
|
||||
assertSingleToken(scanner, '..234', 1, 0, '.', TokenType.Delim);
|
||||
});
|
||||
|
||||
test('Token Delim', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '@', 1, 0, '@', TokenType.Delim);
|
||||
assertSingleToken(scanner, '+', 1, 0, '+', TokenType.Delim);
|
||||
assertSingleToken(scanner, '>', 1, 0, '>', TokenType.Delim);
|
||||
assertSingleToken(scanner, '#', 1, 0, '#', TokenType.Delim);
|
||||
assertSingleToken(scanner, '\'', 1, 0, '\'', TokenType.BadString);
|
||||
assertSingleToken(scanner, '"', 1, 0, '"', TokenType.BadString);
|
||||
});
|
||||
|
||||
test('Token Hash', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '#import', 7, 0, '#import', TokenType.Hash);
|
||||
assertSingleToken(scanner, '#-mport', 7, 0, '#-mport', TokenType.Hash);
|
||||
assertSingleToken(scanner, '#123', 4, 0, '#123', TokenType.Hash);
|
||||
});
|
||||
|
||||
test('Token Dimension/Percentage', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '3em', 3, 0, '3em', TokenType.EMS);
|
||||
assertSingleToken(scanner, '4.423ex', 7, 0, '4.423ex', TokenType.EXS);
|
||||
assertSingleToken(scanner, '3423px', 6, 0, '3423px', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423cm', 7, 0, '4.423cm', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423mm', 7, 0, '4.423mm', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423in', 7, 0, '4.423in', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423pt', 7, 0, '4.423pt', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423pc', 7, 0, '4.423pc', TokenType.Length);
|
||||
assertSingleToken(scanner, '4.423deg', 8, 0, '4.423deg', TokenType.Angle);
|
||||
assertSingleToken(scanner, '4.423rad', 8, 0, '4.423rad', TokenType.Angle);
|
||||
assertSingleToken(scanner, '4.423grad', 9, 0, '4.423grad', TokenType.Angle);
|
||||
assertSingleToken(scanner, '4.423ms', 7, 0, '4.423ms', TokenType.Time);
|
||||
assertSingleToken(scanner, '4.423s', 6, 0, '4.423s', TokenType.Time);
|
||||
assertSingleToken(scanner, '4.423hz', 7, 0, '4.423hz', TokenType.Freq);
|
||||
assertSingleToken(scanner, '.423khz', 7, 0, '.423khz', TokenType.Freq);
|
||||
assertSingleToken(scanner, '3.423%', 6, 0, '3.423%', TokenType.Percentage);
|
||||
assertSingleToken(scanner, '.423%', 5, 0, '.423%', TokenType.Percentage);
|
||||
assertSingleToken(scanner, '.423ft', 6, 0, '.423ft', TokenType.Dimension);
|
||||
assertSingleToken(scanner, '200dpi', 6, 0, '200dpi', TokenType.Resolution);
|
||||
assertSingleToken(scanner, '123dpcm', 7, 0, '123dpcm', TokenType.Resolution);
|
||||
});
|
||||
|
||||
test('Token String', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '\'farboo\'', 8, 0, '\'farboo\'', TokenType.String);
|
||||
assertSingleToken(scanner, '"farboo"', 8, 0, '"farboo"', TokenType.String);
|
||||
assertSingleToken(scanner, '"farbo\u00f0"', 8, 0, '"farbo\u00f0"', TokenType.String);
|
||||
assertSingleToken(scanner, '"far\\\"oo"', 9, 0, '"far\"oo"', TokenType.String);
|
||||
assertSingleToken(scanner, '"fa\\\noo"', 8, 0, '"fa\noo"', TokenType.String);
|
||||
assertSingleToken(scanner, '"fa\\\roo"', 8, 0, '"fa\roo"', TokenType.String);
|
||||
assertSingleToken(scanner, '"fa\\\foo"', 8, 0, '"fa\foo"', TokenType.String);
|
||||
assertSingleToken(scanner, '\'farboo"', 8, 0, '\'farboo"', TokenType.BadString);
|
||||
assertSingleToken(scanner, '\'farboo', 7, 0, '\'farboo', TokenType.BadString);
|
||||
assertSingleToken(scanner, '\'', 1, 0, '\'', TokenType.BadString);
|
||||
assertSingleToken(scanner, '"', 1, 0, '"', TokenType.BadString);
|
||||
});
|
||||
|
||||
test('Token CDO', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '<!--', 4, 0, '<!--', TokenType.CDO);
|
||||
assertSingleToken(scanner, '<!-\n-', 1, 0, '<', TokenType.Delim);
|
||||
});
|
||||
|
||||
test('Token CDC', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '-->', 3, 0, '-->', TokenType.CDC);
|
||||
assertSingleToken(scanner, '--y>', 3, 0, '--y', TokenType.Ident);
|
||||
assertSingleToken(scanner, '--<', 1, 0, '-', TokenType.Delim);
|
||||
});
|
||||
|
||||
test('Token singletokens ;:{}[]()', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, ': ', 1, 0, ':', TokenType.Colon);
|
||||
assertSingleToken(scanner, '; ', 1, 0, ';', TokenType.SemiColon);
|
||||
assertSingleToken(scanner, '{ ', 1, 0, '{', TokenType.CurlyL);
|
||||
assertSingleToken(scanner, '} ', 1, 0, '}', TokenType.CurlyR);
|
||||
assertSingleToken(scanner, '[ ', 1, 0, '[', TokenType.BracketL);
|
||||
assertSingleToken(scanner, '] ', 1, 0, ']', TokenType.BracketR);
|
||||
assertSingleToken(scanner, '( ', 1, 0, '(', TokenType.ParenthesisL);
|
||||
assertSingleToken(scanner, ') ', 1, 0, ')', TokenType.ParenthesisR);
|
||||
});
|
||||
|
||||
test('Token dashmatch & includes', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '~=', 2, 0, '~=', TokenType.Includes);
|
||||
assertSingleToken(scanner, '~', 1, 0, '~', TokenType.Delim);
|
||||
assertSingleToken(scanner, '|=', 2, 0, '|=', TokenType.Dashmatch);
|
||||
assertSingleToken(scanner, '|', 1, 0, '|', TokenType.Delim);
|
||||
assertSingleToken(scanner, '^=', 2, 0, '^=', TokenType.PrefixOperator);
|
||||
assertSingleToken(scanner, '$=', 2, 0, '$=', TokenType.SuffixOperator);
|
||||
assertSingleToken(scanner, '*=', 2, 0, '*=', TokenType.SubstringOperator);
|
||||
});
|
||||
|
||||
test('Comments', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, '/* */', 0, 10, '', TokenType.EOF);
|
||||
assertSingleToken(scanner, '/* abcd*/', 0, 14, '', TokenType.EOF);
|
||||
assertSingleToken(scanner, '/*abcd */', 0, 10, '', TokenType.EOF);
|
||||
assertSingleToken(scanner, '/* ab- .-cd */', 0, 15, '', TokenType.EOF);
|
||||
});
|
||||
|
||||
test('Whitespaces', function () {
|
||||
let scanner = new Scanner();
|
||||
assertSingleToken(scanner, ' ', 0, 1, '', TokenType.EOF);
|
||||
assertSingleToken(scanner, ' ', 0, 6, '', TokenType.EOF);
|
||||
});
|
||||
});
|
||||
|
||||
suite('CSS - Token Sequences', () => {
|
||||
|
||||
function assertTokenSequence(scan: Scanner, source: string, ...tokens: TokenType[]): void {
|
||||
scan.setSource(source);
|
||||
let token = scan.scan();
|
||||
let i = 0;
|
||||
while (tokens.length > i) {
|
||||
assert.equal(token.type, tokens[i]);
|
||||
token = scan.scan();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// tests with skipping comments
|
||||
test('Token Sequence', function () {
|
||||
let scanner = new Scanner();
|
||||
assertTokenSequence(scanner, '5 5 5 5', TokenType.Num, TokenType.Num, TokenType.Num, TokenType.Num);
|
||||
assertTokenSequence(scanner, '/* 5 4 */-->', TokenType.CDC);
|
||||
assertTokenSequence(scanner, '/* 5 4 */ -->', TokenType.CDC);
|
||||
assertTokenSequence(scanner, '/* "adaasd" */ -->', TokenType.CDC);
|
||||
assertTokenSequence(scanner, '/* <!-- */ -->', TokenType.CDC);
|
||||
assertTokenSequence(scanner, 'red-->', TokenType.Ident, TokenType.Delim);
|
||||
assertTokenSequence(scanner, '@ import', TokenType.Delim, TokenType.Ident);
|
||||
});
|
||||
|
||||
});
|
106
extensions/css/server/src/test/css/selectorPrinting.test.ts
Normal file
106
extensions/css/server/src/test/css/selectorPrinting.test.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import * as selectorPrinter from '../../services/selectorPrinting';
|
||||
import {TextDocument} from 'vscode-languageserver';
|
||||
|
||||
function elementToString(element: selectorPrinter.Element): string {
|
||||
let label = element.name || '';
|
||||
if (element.attributes) {
|
||||
label = label + '[';
|
||||
let needsSeparator = false;
|
||||
for (let key in element.attributes) {
|
||||
if (needsSeparator) {
|
||||
label = label + '|';
|
||||
}
|
||||
needsSeparator = true;
|
||||
label = label + key + '=' + element.attributes[key];
|
||||
}
|
||||
label = label + ']';
|
||||
}
|
||||
|
||||
|
||||
if (element.children) {
|
||||
label = label + '{';
|
||||
for (let index = 0; index < element.children.length; index++) {
|
||||
if (index > 0) {
|
||||
label = label + '|';
|
||||
}
|
||||
label = label + elementToString(element.children[index]);
|
||||
}
|
||||
label = label + '}';
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
export function parseSelector(p: Parser, input: string, selectorName: string, expected: string): void {
|
||||
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
|
||||
let styleSheet = p.parseStylesheet(document);
|
||||
|
||||
let node = nodes.getNodeAtOffset(styleSheet, input.indexOf(selectorName));
|
||||
let selector = node.findParent(nodes.NodeType.Selector);
|
||||
|
||||
let element = selectorPrinter.selectorToElement(selector);
|
||||
assert.equal(elementToString(element), expected);
|
||||
}
|
||||
|
||||
export interface ExpectedElement {
|
||||
name?: string;
|
||||
attributes?: { [name: string]: string; };
|
||||
}
|
||||
|
||||
export function assertElement(p: Parser, input: string, element: ExpectedElement): void {
|
||||
let node = p.internalParse(input, p._parseSimpleSelector);
|
||||
|
||||
let actual = selectorPrinter.toElement(node);
|
||||
|
||||
assert.equal(actual.name, element.name);
|
||||
assert.deepEqual(actual.attributes, element.attributes);
|
||||
}
|
||||
|
||||
|
||||
suite('CSS - Selector Printing', () => {
|
||||
|
||||
test('class/hash/elementname/attr', function () {
|
||||
let p = new Parser();
|
||||
assertElement(p, 'element', { name: 'element' });
|
||||
assertElement(p, '.div', { attributes: { class: 'div' } });
|
||||
assertElement(p, '#first', { attributes: { id: 'first' } });
|
||||
assertElement(p, 'element.on', { name: 'element', attributes: { class: 'on' } });
|
||||
assertElement(p, 'element.on#first', { name: 'element', attributes: { class: 'on', id: 'first' } });
|
||||
assertElement(p, '.on#first', { attributes: { class: 'on', id: 'first' } });
|
||||
|
||||
assertElement(p, '[lang=\'de\']', { attributes: { lang: 'de' } });
|
||||
assertElement(p, '[enabled]', { attributes: { enabled: undefined } });
|
||||
|
||||
});
|
||||
|
||||
test('simple selector', function () {
|
||||
let p = new Parser();
|
||||
parseSelector(p, 'element { }', 'element', '{element}');
|
||||
parseSelector(p, 'element.div { }', 'element', '{element[class=div]}');
|
||||
parseSelector(p, 'element.on#first { }', 'element', '{element[class=on|id=first]}');
|
||||
parseSelector(p, 'element:hover { }', 'element', '{element[:hover=]}');
|
||||
parseSelector(p, 'element[lang=\'de\'] { }', 'element', '{element[lang=de]}');
|
||||
parseSelector(p, 'element[enabled] { }', 'element', '{element[enabled=undefined]}');
|
||||
parseSelector(p, 'element[foo~="warning"] { }', 'element', '{element[foo= … warning … ]}');
|
||||
parseSelector(p, 'element[lang|="en"] { }', 'element', '{element[lang=en-…]}');
|
||||
parseSelector(p, '* { }', '*', '{element}');
|
||||
});
|
||||
|
||||
test('selector', function () {
|
||||
let p = new Parser();
|
||||
parseSelector(p, 'e1 e2 { }', 'e1', '{e1{…{e2}}}');
|
||||
parseSelector(p, 'e1 .div { }', 'e1', '{e1{…{[class=div]}}}');
|
||||
parseSelector(p, 'e1 > e2 { }', 'e2', '{e1{e2}}');
|
||||
parseSelector(p, 'e1, e2 { }', 'e1', '{e1}');
|
||||
parseSelector(p, 'e1 + e2 { }', 'e2', '{e1|e2}');
|
||||
parseSelector(p, 'e1 ~ e2 { }', 'e2', '{e1|e2|⋮|e2}');
|
||||
});
|
||||
});
|
73
extensions/css/server/src/test/less/lessCompletion.test.ts
Normal file
73
extensions/css/server/src/test/less/lessCompletion.test.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import {LESSParser} from '../../parser/lessParser';
|
||||
import {LESSCompletion} from '../../services/lessCompletion';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {TextDocument, Position} from 'vscode-languageserver';
|
||||
import {assertCompletion, ItemDescription} from '../css/completion.test';
|
||||
|
||||
suite('LESS - Completions', () => {
|
||||
|
||||
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<void> {
|
||||
let idx = stringBefore ? value.indexOf(stringBefore) + stringBefore.length : 0;
|
||||
|
||||
let completionProvider = new LESSCompletion();
|
||||
|
||||
let document = TextDocument.create('test://test/test.less', 'less', 0, value);
|
||||
let position = Position.create(0, idx);
|
||||
let jsonDoc = new LESSParser().parseStylesheet(document);
|
||||
return completionProvider.doComplete(document, position, jsonDoc).then(list => {
|
||||
if (expected.count) {
|
||||
assert.equal(list.items, expected.count);
|
||||
}
|
||||
if (expected.items) {
|
||||
for (let item of expected.items) {
|
||||
assertCompletion(list, item, document);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('sylesheet', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('body { ', '{ ', {
|
||||
items: [
|
||||
{ label: 'display' },
|
||||
{ label: 'background' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { ver', 'ver', {
|
||||
items: [
|
||||
{ label: 'vertical-align' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { word-break: ', ': ', {
|
||||
items: [
|
||||
{ label: 'keep-all' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('body { inner { vertical-align: }', ': ', {
|
||||
items: [
|
||||
{ label: 'bottom' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('@var1: 3; body { inner { vertical-align: }', 'align: ', {
|
||||
items: [
|
||||
{ label: '@var1' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { background-color: d', 'background-color: d', {
|
||||
items: [
|
||||
{ label: 'darken' },
|
||||
{ label: 'desaturate' }
|
||||
]
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
55
extensions/css/server/src/test/less/lessNavigation.ts
Normal file
55
extensions/css/server/src/test/less/lessNavigation.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {LESSParser} from '../../parser/lessParser';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {assertScopeBuilding, assertSymbolsInScope, assertScopesAndSymbols, assertHighlights} from '../css/navigation.test';
|
||||
|
||||
suite('LESS - Symbols', () => {
|
||||
|
||||
test('scope building', function () {
|
||||
let p = new LESSParser();
|
||||
assertScopeBuilding(p, '@let: blue');
|
||||
assertScopeBuilding(p, '.class { .nested {} }', { offset: 7, length: 14 }, { offset: 17, length: 2 });
|
||||
});
|
||||
|
||||
test('symbols in scopes', function () {
|
||||
let p = new LESSParser();
|
||||
assertSymbolsInScope(p, '@let: iable;', 0, { name: '@let', type: nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '@let: iable;', 11, { name: '@let', type: nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 11, { name: '@let', type: nodes.ReferenceType.Variable }, { name: '.class', type: nodes.ReferenceType.Rule });
|
||||
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 21, { name: '@color', type: nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 36, { name: '@color', type: nodes.ReferenceType.Variable });
|
||||
|
||||
assertSymbolsInScope(p, '@namespace "x"; .mixin() {}', 0, { name: '.mixin', type: nodes.ReferenceType.Mixin });
|
||||
assertSymbolsInScope(p, '.mixin() { .nested() {} }', 10, { name: '.nested', type: nodes.ReferenceType.Mixin });
|
||||
assertSymbolsInScope(p, '.mixin() { .nested() {} }', 11);
|
||||
|
||||
assertSymbolsInScope(p, '@keyframes animation {};', 0, { name: 'animation', type: nodes.ReferenceType.Keyframe });
|
||||
});
|
||||
|
||||
test('scopes and symbols', function () {
|
||||
let p = new LESSParser();
|
||||
assertScopesAndSymbols(p, '@var1: 1; @var2: 2; .foo { @var3: 3; }', '@var1,@var2,.foo,[@var3]');
|
||||
assertScopesAndSymbols(p, '.mixin1 { @var0: 1} .mixin2(@var1) { @var3: 3 }', '.mixin1,.mixin2,[@var0],[@var1,@var3]');
|
||||
assertScopesAndSymbols(p, 'a b { @var0: 1; c { d { } } }', '[@var0,c,[d,[]]]');
|
||||
});
|
||||
|
||||
test('mark highlights', function (testDone) {
|
||||
let p = new LESSParser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '@var1: 1; @var2: /**/@var1;', '/**/', 2, 1, '@var1'),
|
||||
assertHighlights(p, '@var1: 1; p { @var2: /**/@var1; }', '/**/', 2, 1, '@var1'),
|
||||
assertHighlights(p, 'r1 { @var1: 1; p1: @var1;} r2,r3 { @var1: 1; p1: /**/@var1 + @var1;}', '/**/', 3, 1, '@var1'),
|
||||
assertHighlights(p, '.r1 { r1: 1em; } r2 { r1: 2em; /**/.r1;}', '/**/', 2, 1, '.r1'),
|
||||
assertHighlights(p, '.r1(@p1) { r1: @p1; } r2 { r1: 2em; /**/.r1(2px); }', '/**/', 2, 1, '.r1'),
|
||||
assertHighlights(p, '/**/.r1(@p1) { r1: @p1; } r2 { r1: 2em; .r1(2px); }', '/**/', 2, 1, '.r1'),
|
||||
assertHighlights(p, '@p1 : 1; .r1(@p1) { r1: /**/@p1; }', '/**/', 2, 1, '@p1'),
|
||||
assertHighlights(p, '/**/@p1 : 1; .r1(@p1) { r1: @p1; }', '/**/', 1, 1, '@p1'),
|
||||
assertHighlights(p, '@p1 : 1; .r1(/**/@p1) { r1: @p1; }', '/**/', 2, 1, '@p1'),
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
24
extensions/css/server/src/test/less/nodes.test.ts
Normal file
24
extensions/css/server/src/test/less/nodes.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {assertNodes} from '../css/nodes.test';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {LESSParser} from '../../parser/lessParser';
|
||||
|
||||
suite('LESS - Nodes', () => {
|
||||
|
||||
function ruleset(input: string): nodes.RuleSet {
|
||||
let parser = new LESSParser();
|
||||
let node = parser.internalParse(input, parser._parseRuleset);
|
||||
return node;
|
||||
}
|
||||
|
||||
test('RuleSet', function () {
|
||||
assertNodes(ruleset, 'selector { prop: value }', 'ruleset,...,selector,...,declaration,...,property,...,expression');
|
||||
assertNodes(ruleset, 'selector { prop; }', 'ruleset,...,selector,...,selector');
|
||||
assertNodes(ruleset, 'selector { prop {} }', 'ruleset,...,ruleset');
|
||||
});
|
||||
});
|
218
extensions/css/server/src/test/less/parser.test.ts
Normal file
218
extensions/css/server/src/test/less/parser.test.ts
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Parser} from '../../parser/cssParser';
|
||||
import {TokenType} from '../../parser/cssScanner';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {ParseError} from '../../parser/cssErrors';
|
||||
import {LESSParser} from '../../parser/lessParser';
|
||||
|
||||
import {assertNode, assertNoNode, assertError} from '../css/parser.test';
|
||||
|
||||
suite('LESS - Parser', () => {
|
||||
|
||||
test('Variable', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('@color', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('@co42lor', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('@-co42lor', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('@@foo', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('@@@foo', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('@12ooo', parser, parser._parseVariable.bind(parser));
|
||||
assertNoNode('@ @foo', parser, parser._parseFunction.bind(parser));
|
||||
assertNoNode('@-@foo', parser, parser._parseFunction.bind(parser));
|
||||
});
|
||||
|
||||
test('Media', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('@media @phone {}', parser, parser._parseMedia.bind(parser));
|
||||
});
|
||||
|
||||
test('VariableDeclaration', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('@color: #F5F5F5', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@color: 0', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@color: 255', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@color: 25.5', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@color: 25px', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@color: 25.5px', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('@greeting: `"hello".toUpperCase() + "!";`', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
});
|
||||
|
||||
test('MixinDeclaration', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.color (@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.color(@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.color(@color) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.color(@color; @border) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.color() { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.color( ) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@a) when (@a > 10), (@a < -10) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@a) when (isnumber(@a)) and (@a > 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@b) when not (@b >= 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@b) when not (@b > 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@a, @rest...) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
assertNode('.mixin (@a) when (lightness(@a) >= 50%) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
|
||||
|
||||
});
|
||||
|
||||
test('MixinReference', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.box-shadow(0 0 5px, 30%)', parser, parser._parseMixinReference.bind(parser));
|
||||
assertNode('.box-shadow', parser, parser._parseMixinReference.bind(parser));
|
||||
assertNode('.mixin(10) !important', parser, parser._parseMixinReference.bind(parser));
|
||||
});
|
||||
|
||||
test('MixinParameter', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('@_', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('@let: value', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('@let', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('@rest...', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('...', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('value', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('"string"', parser, parser._parseMixinParameter.bind(parser));
|
||||
assertNode('50%', parser, parser._parseMixinParameter.bind(parser));
|
||||
});
|
||||
|
||||
test('Parser - function', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('%()', parser, parser._parseFunction.bind(parser));
|
||||
assertNoNode('% ()', parser, parser._parseFunction.bind(parser));
|
||||
|
||||
});
|
||||
|
||||
test('Expr', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('(@let + 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@let - 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@let * 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@let / 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 - @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 * @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 / @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 / 20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + @let + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@var1 + @var2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('((@let + 5) * 2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('((@let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('@color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('@color, @color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('@color, 42%', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('@color, 42%, @color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('@color - (@color + 10%)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(@base + @filler)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(100% / 2 + @filler)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('100% / 2 + @filler', parser, parser._parseExpr.bind(parser));
|
||||
});
|
||||
|
||||
test('LessOperator', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('>=', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('>', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('<', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('=<', parser, parser._parseOperator.bind(parser));
|
||||
});
|
||||
|
||||
test('Extend', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('nav { &:extend(.inline); }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Declaration', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('border: thin solid 1px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: @color', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: blue', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: (20 / @let)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: (20 / 20 + @let)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: func(@red)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: desaturate(@red, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: desaturate(16, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
|
||||
assertNode('color: @base-color + #111', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: 100% / 2 + @ref', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('border: (@width * 2) solid black', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('property: @class', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('prop-erty: fnc(@t, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
});
|
||||
|
||||
test('Stylesheet', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.color (@radius: 5px){ -border-radius: @radius }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
|
||||
|
||||
assertNode('.mixin (@a, @rest...) {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.mixin (@a) when (lightness(@a) >= 50%) { background-color: black;}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.some-mixin { font-weight:bold; } h1 { .some-mixin; font-size:40px; }', parser, parser._parseStylesheet.bind(parser));
|
||||
|
||||
assertNode('@color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@color: #F5F5F5; @color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@color: #F5F5F5; .color (@radius: 5px) { -border-radius: #F5F5F5 } @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@import-once "lib";', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@import-once (css) "hello";', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('@import-once () "hello";', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@import-once (less);', parser, parser._parseStylesheet.bind(parser), ParseError.URIOrStringExpected);
|
||||
});
|
||||
|
||||
test('Ruleset', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.selector { prop: erty @let 1px; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { .mixin; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { .mixin(1px); .mixin(blue, 1px, \'farboo\') }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { .mixin(blue; 1px;\'farboo\') }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector:active { property:value; nested:hover {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector {}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: declaration }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { @variable: declaration }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { nested {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { nested, a, b {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: value; property: value; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('term', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('%(\'repetitions: %S file: %S\', 1 + 2, "directory/file.less")', parser, parser._parseTerm.bind(parser));
|
||||
assertNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax
|
||||
});
|
||||
|
||||
test('Nested Ruleset', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.class1 { @let: 1; .class { @let: 2; three: @let; let: 3; } one: @let; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('.class1 { @let: 1; > .class2 { display: none; } }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Selector Interpolation', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('.@{name} { }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('~"@{name}" { }', parser, parser._parseRuleset.bind(parser));
|
||||
assertError('~{ }', parser, parser._parseStylesheet.bind(parser), ParseError.StringLiteralExpected);
|
||||
assertError('@', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@{', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@{dd', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.RightCurlyExpected);
|
||||
});
|
||||
|
||||
test('Selector Combinator', function() {
|
||||
let parser = new LESSParser();
|
||||
assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&-foo', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&--&', parser, parser._parseSimpleSelector.bind(parser));
|
||||
});
|
||||
});
|
39
extensions/css/server/src/test/less/scanner.test.ts
Normal file
39
extensions/css/server/src/test/less/scanner.test.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {Scanner, TokenType} from '../../parser/cssScanner';
|
||||
import {LESSScanner} from '../../parser/lessScanner';
|
||||
|
||||
function assertSingleToken(source: string, len: number, offset: number, text: string, type: TokenType): void {
|
||||
let scan = new LESSScanner();
|
||||
scan.setSource(source);
|
||||
let token = scan.scan();
|
||||
assert.equal(token.len, len);
|
||||
assert.equal(token.offset, offset);
|
||||
assert.equal(token.text, text);
|
||||
assert.equal(token.type, type);
|
||||
}
|
||||
|
||||
suite('LESS - Scanner', () => {
|
||||
|
||||
test('Test Escaped JavaScript', function () {
|
||||
assertSingleToken('`', 1, 0, '`', TokenType.BadEscapedJavaScript);
|
||||
assertSingleToken('`a', 2, 0, '`a', TokenType.BadEscapedJavaScript);
|
||||
assertSingleToken('`let a = "ssss"`', 16, 0, '`let a = "ssss"`', TokenType.EscapedJavaScript);
|
||||
assertSingleToken('`let a = "ss\ns"`', 16, 0, '`let a = "ss\ns"`', TokenType.EscapedJavaScript);
|
||||
});
|
||||
|
||||
// less deactivated comments
|
||||
test('Test Token SingleLineComment', function () {
|
||||
assertSingleToken('//', 0, 2, '', TokenType.EOF);
|
||||
assertSingleToken('//this is a comment test', 0, 24, '', TokenType.EOF);
|
||||
assertSingleToken('// this is a comment test', 0, 25, '', TokenType.EOF);
|
||||
assertSingleToken('// this is a\na', 1, 13, 'a', TokenType.Ident);
|
||||
assertSingleToken('// this is a\n// more\n \n/* comment */a', 1, 38, 'a', TokenType.Ident);
|
||||
});
|
||||
});
|
336
extensions/css/server/src/test/scss/example.scss
Normal file
336
extensions/css/server/src/test/scss/example.scss
Normal file
|
@ -0,0 +1,336 @@
|
|||
// snippets from the scss documentation at http://sass-lang.com/
|
||||
|
||||
/* css stuff */
|
||||
/* charset */
|
||||
@charset "UTF-8";
|
||||
|
||||
/* nested rules */
|
||||
#main {
|
||||
width: 97%;
|
||||
p, div {
|
||||
font-size: 2em;
|
||||
a { font-weight: bold; }
|
||||
}
|
||||
pre { font-size: 3em; }
|
||||
}
|
||||
|
||||
/* parent selector (&) */
|
||||
#main {
|
||||
color: black;
|
||||
a {
|
||||
font-weight: bold;
|
||||
&:hover { color: red; }
|
||||
}
|
||||
}
|
||||
|
||||
/* nested properties */
|
||||
.funky {
|
||||
font: 2px/3px {
|
||||
family: fantasy;
|
||||
size: 30em;
|
||||
weight: bold;
|
||||
}
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* nesting conflicts */
|
||||
tr.default {
|
||||
foo: { // properties
|
||||
foo : 1;
|
||||
}
|
||||
foo: 1px; // rule
|
||||
foo.bar { // selector
|
||||
foo : 1;
|
||||
}
|
||||
foo:bar { // selector
|
||||
foo : 1;
|
||||
}
|
||||
foo: 1px; // rule
|
||||
}
|
||||
|
||||
/* extended comment syntax */
|
||||
/* This comment is
|
||||
* several lines long.
|
||||
* since it uses the CSS comment syntax,
|
||||
* it will appear in the CSS output. */
|
||||
body { color: black; }
|
||||
|
||||
// These comments are only one line long each.
|
||||
// They won't appear in the CSS output,
|
||||
// since they use the single-line comment syntax.
|
||||
a { color: green; }
|
||||
|
||||
/* variables */
|
||||
$width: 5em;
|
||||
$width: "Second width?" !default;
|
||||
#main {
|
||||
$localvar: 6em;
|
||||
width: $width;
|
||||
|
||||
$font-size: 12px;
|
||||
$line-height: 30px;
|
||||
font: #{$font-size}/#{$line-height};
|
||||
}
|
||||
$name: foo;
|
||||
$attr: border;
|
||||
p.#{$name} {
|
||||
#{$attr}-color: blue;
|
||||
}
|
||||
|
||||
/* variable declaration with whitespaces */
|
||||
// Set the color of your columns
|
||||
$grid-background-column-color : rgba(100, 100, 225, 0.25) !default;
|
||||
|
||||
/* operations*/
|
||||
p {
|
||||
width: (1em + 2em) * 3;
|
||||
color: #010203 + #040506;
|
||||
font-family: sans- + "serif";
|
||||
margin: 3px + 4px auto;
|
||||
content: "I ate #{5 + 10} pies!";
|
||||
color: hsl(0, 100%, 50%);
|
||||
color: hsl($hue: 0, $saturation: 100%, $lightness: 50%);
|
||||
}
|
||||
/* functions*/
|
||||
$grid-width: 40px;
|
||||
$gutter-width: 10px;
|
||||
@function grid-width($n) {
|
||||
@return $n * $grid-width + ($n - 1) * $gutter-width;
|
||||
}
|
||||
#sidebar { width: grid-width(5); }
|
||||
|
||||
/* @import */
|
||||
@import "foo.scss";
|
||||
$family: unquote("Droid+Sans");
|
||||
@import "rounded-corners", url("http://fonts.googleapis.com/css?family=#{$family}");
|
||||
#main {
|
||||
@import "example";
|
||||
}
|
||||
|
||||
/* @media */
|
||||
.sidebar {
|
||||
width: 300px;
|
||||
@media screen and (orientation: landscape) {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
/* @extend */
|
||||
.error {
|
||||
border: 1px #f00;
|
||||
background-color: #fdd;
|
||||
}
|
||||
.seriousError {
|
||||
@extend .error;
|
||||
border-width: 3px;
|
||||
}
|
||||
#context a%extreme {
|
||||
color: blue;
|
||||
font-weight: bold;
|
||||
font-size: 2em;
|
||||
}
|
||||
.notice {
|
||||
@extend %extreme !optional;
|
||||
}
|
||||
|
||||
/* @debug and @warn */
|
||||
@debug 10em + 12em;
|
||||
@mixin adjust-location($x, $y) {
|
||||
@if unitless($x) {
|
||||
@warn "Assuming #{$x} to be in pixels";
|
||||
$x: 1px * $x;
|
||||
}
|
||||
@if unitless($y) {
|
||||
@warn "Assuming #{$y} to be in pixels";
|
||||
$y: 1px * $y;
|
||||
}
|
||||
position: relative; left: $x; top: $y;
|
||||
}
|
||||
|
||||
/* control directives */
|
||||
|
||||
/* if statement */
|
||||
p {
|
||||
@if 1 + 1 == 2 { border: 1px solid; }
|
||||
@if 5 < 3 { border: 2px dotted; }
|
||||
@if null { border: 3px double; }
|
||||
}
|
||||
|
||||
/* if else statement */
|
||||
$type: monster;
|
||||
p {
|
||||
@if $type == ocean {
|
||||
color: blue;
|
||||
} @else {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
/* for statement */
|
||||
@for $i from 1 through 3 {
|
||||
.item-#{$i} { width: 2em * $i; }
|
||||
}
|
||||
|
||||
/* each statement */
|
||||
@each $animal in puma, sea-slug, egret, salamander {
|
||||
.#{$animal}-icon {
|
||||
background-image: url('/images/#{$animal}.png');
|
||||
}
|
||||
}
|
||||
|
||||
/* while statement */
|
||||
$i: 6;
|
||||
@while $i > 0 {
|
||||
.item-#{$i} { width: 2em * $i; }
|
||||
$i: $i - 2;
|
||||
}
|
||||
|
||||
/* function with controlstatements */
|
||||
@function foo($total, $a) {
|
||||
@for $i from 0 to $total {
|
||||
@if (unit($a) == "%") and ($i == ($total - 1)) {
|
||||
$z: 100%;
|
||||
@return '1';
|
||||
}
|
||||
}
|
||||
@return $grid;
|
||||
}
|
||||
|
||||
/* @mixin simple*/
|
||||
@mixin large-text {
|
||||
font: {
|
||||
family: Arial;
|
||||
size: 20px;
|
||||
weight: bold;
|
||||
}
|
||||
color: #ff0000;
|
||||
}
|
||||
.page-title {
|
||||
@include large-text;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* mixin with parameters */
|
||||
@mixin sexy-border($color, $width: 1in) {
|
||||
border: {
|
||||
color: $color;
|
||||
width: $width;
|
||||
style: dashed;
|
||||
}
|
||||
}
|
||||
p { @include sexy-border(blue); }
|
||||
|
||||
/* mixin with varargs */
|
||||
@mixin box-shadow($shadows...) {
|
||||
-moz-box-shadow: $shadows;
|
||||
-webkit-box-shadow: $shadows;
|
||||
box-shadow: $shadows;
|
||||
}
|
||||
.shadows {
|
||||
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
|
||||
}
|
||||
|
||||
/* include with varargs */
|
||||
@mixin colors($text, $background, $border) {
|
||||
color: $text;
|
||||
background-color: $background;
|
||||
border-color: $border;
|
||||
}
|
||||
$values: #ff0000, #00ff00, #0000ff;
|
||||
.primary {
|
||||
@include colors($values...);
|
||||
}
|
||||
|
||||
/* include with body */
|
||||
@mixin apply-to-ie6-only {
|
||||
* html {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@include apply-to-ie6-only {
|
||||
#logo {
|
||||
background-image: url(/logo.gif);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* attributes */
|
||||
[rel="external"]::after {
|
||||
content: 's';
|
||||
}
|
||||
/*page */
|
||||
@page :left {
|
||||
margin-left: 4cm;
|
||||
margin-right: 3cm;
|
||||
}
|
||||
|
||||
/* missing semicolons */
|
||||
tr.default {
|
||||
foo.bar {
|
||||
$foo: 1px
|
||||
}
|
||||
foo: {
|
||||
foo : white
|
||||
}
|
||||
foo.bar1 {
|
||||
@extend tr.default
|
||||
}
|
||||
foo.bar2 {
|
||||
@import "compass"
|
||||
}
|
||||
bar: black
|
||||
}
|
||||
|
||||
/* rules without whitespace */
|
||||
legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}
|
||||
|
||||
/* extend with interpolation variable */
|
||||
@mixin error($a: false) {
|
||||
@extend .#{$a};
|
||||
@extend ##{$a};
|
||||
}
|
||||
#bar {a: 1px;}
|
||||
.bar {b: 1px;}
|
||||
foo {
|
||||
@include error('bar');
|
||||
}
|
||||
|
||||
/* css3: @font face */
|
||||
@font-face { font-family: Delicious; src: url('Delicious-Roman.otf'); }
|
||||
|
||||
/* rule names with variables */
|
||||
.orbit-#{$d}-prev {
|
||||
#{$d}-style: 0;
|
||||
foo-#{$d}: 1;
|
||||
#{$d}-bar-#{$d}: 2;
|
||||
foo-#{$d}-bar: 1;
|
||||
}
|
||||
|
||||
/* keyframes */
|
||||
@-webkit-keyframes NAME-YOUR-ANIMATION {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
@-moz-keyframes NAME-YOUR-ANIMATION {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
@-o-keyframes NAME-YOUR-ANIMATION {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
@keyframes NAME-YOUR-ANIMATION {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* string escaping */
|
||||
[data-icon='test-1']:before {
|
||||
content:'\\';
|
||||
}
|
||||
/* a comment */
|
||||
$var1: '\'';
|
||||
$var2: "\"";
|
||||
/* another comment */
|
||||
|
24
extensions/css/server/src/test/scss/languageFacts.test.ts
Normal file
24
extensions/css/server/src/test/scss/languageFacts.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
import {assertColor} from '../css/languageFacts.test';
|
||||
|
||||
suite('SCSS - Language facts', () => {
|
||||
|
||||
test('is color', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertColor(parser, '#main { color: foo(red) }', 'red', true);
|
||||
assertColor(parser, '#main { color: red() }', 'red', false);
|
||||
assertColor(parser, '#main { red { nested: 1px } }', 'red', false);
|
||||
assertColor(parser, '#main { @include red; }', 'red', false);
|
||||
assertColor(parser, '#main { @include foo($f: red); }', 'red', true);
|
||||
assertColor(parser, '@function red($p) { @return 1px; }', 'red', false);
|
||||
assertColor(parser, '@function foo($p) { @return red; }', 'red', true);
|
||||
assertColor(parser, '@function foo($r: red) { @return $r; }', 'red', true);
|
||||
});
|
||||
});
|
||||
|
52
extensions/css/server/src/test/scss/lint.test.ts
Normal file
52
extensions/css/server/src/test/scss/lint.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {Rule, Rules} from '../../services/lintRules';
|
||||
import {assertEntries} from '../css/lint.test';
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
|
||||
function assertFontFace(input: string, ...rules: Rule[]): void {
|
||||
let p = new SCSSParser();
|
||||
let node = p.internalParse(input, p._parseFontFace);
|
||||
|
||||
assertEntries(node, rules);
|
||||
}
|
||||
|
||||
function assertRuleSet(input: string, ...rules: Rule[]): void {
|
||||
let p = new SCSSParser();
|
||||
let node = p.internalParse(input, p._parseRuleset);
|
||||
assertEntries(node, rules);
|
||||
}
|
||||
|
||||
suite('SCSS - Lint', () => {
|
||||
|
||||
test('empty ruleset', function () {
|
||||
assertRuleSet('selector { color: red; nested {} }', Rules.EmptyRuleSet);
|
||||
});
|
||||
|
||||
test('font-face required properties', function () {
|
||||
assertFontFace('@font-face { }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { src: url(test.tff) }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { font-family: \'name\' }', Rules.RequiredPropertiesForFontFace);
|
||||
assertFontFace('@font-face { font-#{family}: foo }'); // no error, ignore all unknown properties
|
||||
assertFontFace('@font-face { font: {family: foo } }'); // no error, ignore all nested properties
|
||||
assertFontFace('@font-face { @if true { } }'); // no error, ignore all nested properties
|
||||
});
|
||||
|
||||
test('unknown properties', function () {
|
||||
assertRuleSet('selector { -ms-property: "rest is missing" }', Rules.UnknownProperty);
|
||||
assertRuleSet('selector { -moz-box-shadow: "rest is missing" }', Rules.UnknownProperty, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
|
||||
assertRuleSet('selector { box-shadow: none }'); // no error
|
||||
assertRuleSet('selector { -moz-#{box}-shadow: none }'); // no error if theres an interpolation
|
||||
assertRuleSet('selector { outer: { nested : blue }'); // no error for nested
|
||||
});
|
||||
|
||||
test('vendor specific prefixes', function () {
|
||||
assertRuleSet('selector { -moz-animation: none }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
|
||||
assertRuleSet('selector { -moz-transform: none; transform: none }', Rules.AllVendorPrefixes);
|
||||
assertRuleSet('selector { -moz-transform: none; transform: none; -o-transform: none; -webkit-transform: none; -ms-transform: none; }');
|
||||
});
|
||||
});
|
335
extensions/css/server/src/test/scss/parser.test.ts
Normal file
335
extensions/css/server/src/test/scss/parser.test.ts
Normal file
|
@ -0,0 +1,335 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {ParseError} from '../../parser/cssErrors';
|
||||
import {SCSSParseError} from '../../parser/scssErrors';
|
||||
|
||||
import {assertNode, assertError} from '../css/parser.test';
|
||||
|
||||
suite('SCSS - Parser', () => {
|
||||
|
||||
test('Comments', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode(' a { b: /* comment */ c }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode(' a { b: /* comment \n * is several\n * lines long\n */ c }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode(' a { b: // single line comment\n c }', parser, parser._parseStylesheet.bind(parser));
|
||||
});
|
||||
|
||||
test('Variable', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('$color', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('$co42lor', parser, parser._parseVariable.bind(parser));
|
||||
assertNode('$-co42lor', parser, parser._parseVariable.bind(parser));
|
||||
});
|
||||
|
||||
test('VariableDeclaration', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('$color: #F5F5F5', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$color: 0', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$color: 255', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$color: 25.5', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$color: 25px', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$color: 25.5px !default', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertNode('$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseVariableDeclaration.bind(parser));
|
||||
assertError('$color: red !def', parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword);
|
||||
assertError('$color : !default', parser, parser._parseVariableDeclaration.bind(parser), ParseError.VariableValueExpected);
|
||||
assertError('$color !default', parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected);
|
||||
});
|
||||
|
||||
test('Expr', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('($let + 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($let - 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($let * 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($let / 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 - $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 * $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 / $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 / 20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20 + $let + 20 + 20 + $let)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(20 + 20)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($var1 + $var2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(($let + 5) * 2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(($let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('$color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('$color, $color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('$color, 42%', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('$color, 42%, $color', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('$color - ($color + 10%)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('($base + $filler)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('(100% / 2 + $filler)', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('100% / 2 + $filler', parser, parser._parseExpr.bind(parser));
|
||||
assertNode('not ($v and $b) or $c', parser, parser._parseExpr.bind(parser));
|
||||
|
||||
assertError('(20 + 20', parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected);
|
||||
});
|
||||
|
||||
test('SCSSOperator', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('>=', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('>', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('<', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('<=', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('==', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('!=', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('and', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('+', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('-', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('*', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('/', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('%', parser, parser._parseOperator.bind(parser));
|
||||
assertNode('not', parser, parser._parseUnaryOperator.bind(parser));
|
||||
});
|
||||
|
||||
test('Interpolation', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('#{red}', parser, parser._parseIdent.bind(parser));
|
||||
assertNode('#{$color}', parser, parser._parseIdent.bind(parser));
|
||||
assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser));
|
||||
assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser));
|
||||
assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertError('#{}', parser, parser._parseIdent.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected);
|
||||
});
|
||||
|
||||
test('Declaration', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('border: thin solid 1px', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: $color', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: blue', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: (20 / $let)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: (20 / 20 + $let)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: func($red)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: desaturate($red, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('dummy: desaturate(16, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: $base-color + #111', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: 100% / 2 + $ref', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('border: ($width * 2) solid black', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('property: $class', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('prop-erty: fnc($t, 10%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('width: (1em + 2em) * 3', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: #010203 + #040506', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('margin: 3px + 4px auto', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: hsl(0, 100%, 50%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)', parser, parser._parseDeclaration.bind(parser));
|
||||
assertNode('foo: if($value == \'default\', flex-gutter(), $value)', parser, parser._parseDeclaration.bind(parser));
|
||||
|
||||
assertError('fo = 8', parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected);
|
||||
assertError('fo:', parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected);
|
||||
assertError('color: hsl($hue: 0,', parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('color: hsl($hue: 0', parser, parser._parseDeclaration.bind(parser), ParseError.RightParenthesisExpected);
|
||||
});
|
||||
|
||||
test('Stylesheet', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('$color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('$color: #F5F5F5; $color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('#main { width: 97%; p, div { a { font-weight: bold; } } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('a { &:hover { color: red; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('fo { font: 2px/3px { family: fantasy; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.foo { bar: { yoo: fantasy; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin keyframe { @keyframes name { @content; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@include keyframe { 10% { top: 3px; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.class{&--sub-class-with-ampersand{color: red;}}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('fo { font: 2px/3px { family } }', parser, parser._parseStylesheet.bind(parser), ParseError.ColonExpected);
|
||||
});
|
||||
|
||||
test('@import', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@import "test.css"', parser, parser._parseImport.bind(parser));
|
||||
assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser));
|
||||
assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser));
|
||||
assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser));
|
||||
assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser));
|
||||
|
||||
assertError('@import "test.css" "bar.css"', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
|
||||
assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
|
||||
assertError('@import', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
|
||||
});
|
||||
|
||||
test('@media', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media #{$media} and ($feature: $value) {}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('foo { bar { @media screen and (orientation: landscape) {}} }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media screen and (nth($query, 1): nth($query, 2)) { }', parser, parser._parseMedia.bind(parser));
|
||||
});
|
||||
|
||||
test('@keyframe', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@keyframes name { @content; }', parser, parser._parseKeyframe.bind(parser));
|
||||
});
|
||||
|
||||
test('@extend', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('foo { @extend .error; border-width: 3px; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('a.important { @extend .notice !optional; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.hoverlink { @extend a:hover; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.seriousError { @extend .error; @extend .attention; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('#context a%extreme { color: blue; } .notice { @extend %extreme }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@media print { .error { } .seriousError { @extend .error; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }', parser, parser._parseStylesheet.bind(parser));
|
||||
|
||||
assertError('.hoverlink { @extend }', parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected);
|
||||
assertError('.hoverlink { @extend %extreme !default }', parser, parser._parseStylesheet.bind(parser), ParseError.UnknownKeyword);
|
||||
});
|
||||
|
||||
test('@debug', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@debug test;', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('foo { @debug 1 + 4; nested { @warn 1 4; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@if $foo == 1 { @debug 1 + 4 }', parser, parser._parseStylesheet.bind(parser));
|
||||
});
|
||||
|
||||
test('@if', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@if 1 + 1 == 2 { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@if 5 < 3 { border: 2px dotted; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@if null { border: 3px double; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@if 1 <= $let { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@if $i == 1 { p { x: 3px; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('@if { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('@if 1 }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected);
|
||||
});
|
||||
|
||||
test('@for', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@for $k from 1 + $x through 5 + $x { }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertError('@for i from 0 to 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected);
|
||||
assertError('@for $i to 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.FromExpected);
|
||||
assertError('@for $i from 0 by 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.ThroughOrToExpected);
|
||||
assertError('@for $i from {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('@for $i from 0 to {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
});
|
||||
|
||||
test('@each', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@each $i in 1, 2, 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertNode('@each $i in 1 2 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertError('@each i in 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected);
|
||||
assertError('@each $i from 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.InExpected);
|
||||
assertError('@each $i in {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
});
|
||||
|
||||
test('@while', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }', parser, parser._parseRuleSetDeclaration.bind(parser));
|
||||
assertError('@while {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('@while $i != 4', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@while ($i >= 4) {', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.RightCurlyExpected);
|
||||
});
|
||||
|
||||
test('@mixin', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin sexy-border($color, $width: 1in) { color: black; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin apply-to-ie6-only { * html { @content; } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin #{foo}($color){}', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('@mixin $1 {}', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@mixin foo() i {}', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@mixin foo(1) {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('@mixin foo($color = 9) {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('@mixin foo($color)', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
|
||||
assertError('@mixin foo($color){', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
|
||||
assertError('@mixin foo($color,){', parser, parser._parseStylesheet.bind(parser), ParseError.VariableNameExpected);
|
||||
});
|
||||
|
||||
test('@include', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('p { @include sexy-border(blue); }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('.shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('p { @include sexy-border blue', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
|
||||
assertError('p { @include sexy-border($values blue', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('p { @include }', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('p { @include foo($values }', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('p { @include foo($values, }', parser, parser._parseStylesheet.bind(parser), ParseError.ExpressionExpected);
|
||||
|
||||
});
|
||||
|
||||
test('@function', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@function grid-width($n: 1, $e) { @return 0; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertNode('@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }', parser, parser._parseStylesheet.bind(parser));
|
||||
assertError('@function foo {} ', parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected);
|
||||
assertError('@function {} ', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
|
||||
assertError('@function foo($a $b) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('@function foo($a {} ', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
|
||||
assertError('@function foo($a...) { @return; }', parser, parser._parseStylesheet.bind(parser), ParseError.ExpressionExpected);
|
||||
assertError('@function foo($a,) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.VariableNameExpected);
|
||||
assertError('@function foo($a:) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.VariableValueExpected);
|
||||
|
||||
});
|
||||
|
||||
test('Ruleset', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('.selector { prop: erty $let 1px; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector:active { property:value; nested:hover {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector {}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: declaration }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { $variable: declaration }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { nested {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { nested, a, b {}}', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: value; property: $value; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Nested Ruleset', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('.class1 { $let: 1; .class { $let: 2; three: $let; let: 3; } one: $let; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('.class1 { > .class2 { & > .class4 { rule1: v1; } } }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Selector Interpolation', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('.#{$name} { }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('p.#{$name} { #{$attr}-color: blue; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }', parser, parser._parseRuleset.bind(parser));
|
||||
assertNode('##{f} .#{f} #{f}:#{f} { }', parser, parser._parseRuleset.bind(parser));
|
||||
});
|
||||
|
||||
test('Parent Selector', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&-bar', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('&&', parser, parser._parseSimpleSelector.bind(parser));
|
||||
});
|
||||
|
||||
test('Selector Placeholder', function () {
|
||||
let parser = new SCSSParser();
|
||||
assertNode('%hover', parser, parser._parseSimpleSelector.bind(parser));
|
||||
assertNode('a%float', parser, parser._parseSimpleSelector.bind(parser));
|
||||
});
|
||||
});
|
78
extensions/css/server/src/test/scss/scssCompletion.test.ts
Normal file
78
extensions/css/server/src/test/scss/scssCompletion.test.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
import {SCSSCompletion} from '../../services/scssCompletion';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {TextDocument, Position} from 'vscode-languageserver';
|
||||
import {assertCompletion, ItemDescription} from '../css/completion.test';
|
||||
|
||||
suite('SCSS - Completions', () => {
|
||||
|
||||
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<void> {
|
||||
let idx = stringBefore ? value.indexOf(stringBefore) + stringBefore.length : 0;
|
||||
|
||||
let completionProvider = new SCSSCompletion();
|
||||
|
||||
let document = TextDocument.create('test://test/test.scss', 'scss', 0, value);
|
||||
let position = Position.create(0, idx);
|
||||
let jsonDoc = new SCSSParser().parseStylesheet(document);
|
||||
return completionProvider.doComplete(document, position, jsonDoc).then(list => {
|
||||
if (expected.count) {
|
||||
assert.equal(list.items, expected.count);
|
||||
}
|
||||
if (expected.items) {
|
||||
for (let item of expected.items) {
|
||||
assertCompletion(list, item, document);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('sylesheet', function (testDone): any {
|
||||
Promise.all([
|
||||
testCompletionFor('$i: 0; body { width: ', 'width: ', {
|
||||
items: [
|
||||
{ label: '$i' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('@for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } }', '.item-#{', {
|
||||
items: [
|
||||
{ label: '$i' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { background-color: d', 'background-color: d', {
|
||||
items: [
|
||||
{ label: 'darken' },
|
||||
{ label: 'desaturate' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('@function foo($x, $y) { @return $x + $y; } .foo { background-color: f', 'background-color: f', {
|
||||
items: [
|
||||
{ label: 'foo' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { di span { } ', 'di', {
|
||||
items: [
|
||||
{ label: 'display' },
|
||||
{ label: 'div' }
|
||||
]
|
||||
}),
|
||||
testCompletionFor('.foo { .', '{ .', {
|
||||
items: [
|
||||
{ label: '.foo' }
|
||||
]
|
||||
}),
|
||||
// issue #250
|
||||
testCompletionFor('.foo { display: block;', 'block;', {
|
||||
count: 0
|
||||
}),
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
|
||||
});
|
||||
});
|
68
extensions/css/server/src/test/scss/scssNavigation.test.ts
Normal file
68
extensions/css/server/src/test/scss/scssNavigation.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
import * as nodes from '../../parser/cssNodes';
|
||||
import {assertSymbolsInScope, assertScopesAndSymbols, assertHighlights} from '../css/navigation.test';
|
||||
|
||||
suite('SCSS - Symbols', () => {
|
||||
|
||||
test('symbols in scopes', function() {
|
||||
var p = new SCSSParser();
|
||||
assertSymbolsInScope(p, '$var: iable;', 0, { name:'$var', type:nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '$var: iable;', 11, { name:'$var', type:nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 11, { name:'$var', type:nodes.ReferenceType.Variable }, { name:'.class', type:nodes.ReferenceType.Rule });
|
||||
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 22, { name:'$color', type:nodes.ReferenceType.Variable });
|
||||
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 36, { name:'$color', type:nodes.ReferenceType.Variable });
|
||||
|
||||
assertSymbolsInScope(p, '@namespace "x"; @mixin mix() {}', 0, { name:'mix', type:nodes.ReferenceType.Mixin });
|
||||
assertSymbolsInScope(p, '@mixin mix { @mixin nested() {} }', 12, { name:'nested', type:nodes.ReferenceType.Mixin });
|
||||
assertSymbolsInScope(p, '@mixin mix () { @mixin nested() {} }', 13);
|
||||
});
|
||||
|
||||
test('scopes and symbols', function() {
|
||||
var p = new SCSSParser();
|
||||
assertScopesAndSymbols(p, '$var1: 1; $var2: 2; .foo { $var3: 3; }', '$var1,$var2,.foo,[$var3]');
|
||||
assertScopesAndSymbols(p, '@mixin mixin1 { $var0: 1} @mixin mixin2($var1) { $var3: 3 }', 'mixin1,mixin2,[$var0],[$var1,$var3]');
|
||||
assertScopesAndSymbols(p, 'a b { $var0: 1; c { d { } } }', '[$var0,c,[d,[]]]');
|
||||
assertScopesAndSymbols(p, '@function a($p1: 1, $p2: 2) { $v1: 3; @return $v1; }', 'a,[$p1,$p2,$v1]');
|
||||
assertScopesAndSymbols(p, '$var1: 3; @if $var1 == 2 { $var2: 1; } @else { $var2: 2; $var3: 2;} ', '$var1,[$var2],[$var2,$var3]');
|
||||
assertScopesAndSymbols(p, '@if $var1 == 2 { $var2: 1; } @else if $var1 == 2 { $var3: 2; } @else { $var3: 2; } ', '[$var2],[$var3],[$var3]');
|
||||
assertScopesAndSymbols(p, '$var1: 3; @while $var1 < 2 { #rule { a: b; } }', '$var1,[#rule,[]]');
|
||||
assertScopesAndSymbols(p, '$i:0; @each $name in f1, f2, f3 { $i:$i+1; }', '$i,[$name,$i]');
|
||||
assertScopesAndSymbols(p, '$i:0; @for $x from $i to 5 { }', '$i,[$x]');
|
||||
});
|
||||
|
||||
test('mark highlights', function(testDone) {
|
||||
var p = new SCSSParser();
|
||||
Promise.all([
|
||||
assertHighlights(p, '$var1: 1; $var2: /**/$var1;', '$var1', 2, 1),
|
||||
assertHighlights(p, '$var1: 1; p { $var2: /**/$var1; }', '/**/', 2, 1, '$var1'),
|
||||
assertHighlights(p, 'r1 { $var1: 1; p1: $var1;} r2,r3 { $var1: 1; p1: /**/$var1 + $var1;}', '/**/', 3, 1, '$var1'),
|
||||
assertHighlights(p, '.r1 { r1: 1em; } r2 { r1: 2em; @extend /**/.r1;}', '/**/', 2, 1, '.r1'),
|
||||
assertHighlights(p, '/**/%r1 { r1: 1em; } r2 { r1: 2em; @extend %r1;}', '/**/', 2, 1, '%r1'),
|
||||
assertHighlights(p, '@mixin r1 { r1: $p1; } r2 { r2: 2em; @include /**/r1; }', '/**/', 2, 1, 'r1'),
|
||||
assertHighlights(p, '@mixin r1($p1) { r1: $p1; } r2 { r2: 2em; @include /**/r1(2px); }', '/**/', 2, 1, 'r1'),
|
||||
assertHighlights(p, '$p1: 1; @mixin r1($p1: $p1) { r1: $p1; } r2 { r2: 2em; @include /**/r1; }', '/**/', 2, 1, 'r1'),
|
||||
assertHighlights(p, '/**/$p1: 1; @mixin r1($p1: $p1) { r1: $p1; }', '/**/', 2, 1, '$p1'),
|
||||
assertHighlights(p, '$p1 : 1; @mixin r1($p1) { r1: /**/$p1; }', '/**/', 2, 1, '$p1'),
|
||||
assertHighlights(p, '/**/$p1 : 1; @mixin r1($p1) { r1: $p1; }', '/**/', 1, 1, '$p1'),
|
||||
assertHighlights(p, '$p1 : 1; @mixin r1(/**/$p1) { r1: $p1; }', '/**/', 2, 1, '$p1'),
|
||||
assertHighlights(p, '$p1 : 1; @function r1($p1, $p2: /**/$p1) { @return $p1 + $p1 + $p2; }', '/**/', 2, 1, '$p1'),
|
||||
assertHighlights(p, '$p1 : 1; @function r1($p1, /**/$p2: $p1) { @return $p1 + $p2 + $p2; }', '/**/', 3, 1, '$p2'),
|
||||
assertHighlights(p, '@function r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return /**/r1(1, 2); }', '/**/', 2, 1, 'r1'),
|
||||
assertHighlights(p, '@function /**/r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return r1(1, 2); } p { x: r2(); }', '/**/', 2, 1, 'r1'),
|
||||
assertHighlights(p, '@function r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return r1(/**/$p1 : 1, $p2 : 2); } p { x: r2(); }', '/**/', 3, 1, '$p1'),
|
||||
|
||||
assertHighlights(p, '@mixin /*here*/foo { display: inline } foo { @include foo; }', '/*here*/', 2, 1, 'foo'),
|
||||
assertHighlights(p, '@mixin foo { display: inline } foo { @include /*here*/foo; }', '/*here*/', 2, 1, 'foo'),
|
||||
assertHighlights(p, '@mixin foo { display: inline } /*here*/foo { @include foo; }', '/*here*/', 1, 1, 'foo'),
|
||||
assertHighlights(p, '@function /*here*/foo($i) { @return $i*$i; } #foo { width: foo(2); }', '/*here*/', 2, 1, 'foo'),
|
||||
assertHighlights(p, '@function foo($i) { @return $i*$i; } #foo { width: /*here*/foo(2); }', '/*here*/', 2, 1, 'foo')
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
});
|
34
extensions/css/server/src/test/scss/selectorPrinting.test.ts
Normal file
34
extensions/css/server/src/test/scss/selectorPrinting.test.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {SCSSParser} from '../../parser/scssParser';
|
||||
import {parseSelector} from '../css/selectorPrinting.test';
|
||||
|
||||
suite('SCSS - Selector Printing', () => {
|
||||
|
||||
test('nested selector', function () {
|
||||
let p = new SCSSParser();
|
||||
parseSelector(p, 'o1 { e1 { } }', 'e1', '{o1{…{e1}}}');
|
||||
parseSelector(p, 'o1 { e1.div { } }', 'e1', '{o1{…{e1[class=div]}}}');
|
||||
parseSelector(p, 'o1 o2 { e1 { } }', 'e1', '{o1{…{o2{…{e1}}}}}');
|
||||
parseSelector(p, 'o1, o2 { e1 { } }', 'e1', '{o1{…{e1}}}');
|
||||
parseSelector(p, 'o1 { @if $a { e1 { } } }', 'e1', '{o1{…{e1}}}');
|
||||
parseSelector(p, 'o1 { @mixin a { e1 { } } }', 'e1', '{e1}');
|
||||
parseSelector(p, 'o1 { @mixin a { e1 { } } }', 'e1', '{e1}');
|
||||
});
|
||||
|
||||
test('referencing selector', function () {
|
||||
let p = new SCSSParser();
|
||||
parseSelector(p, 'o1 { &:hover { }}', '&', '{o1[:hover=]}');
|
||||
parseSelector(p, 'o1 { &:hover & { }}', '&', '{o1[:hover=]{…{o1}}}');
|
||||
});
|
||||
|
||||
test('placeholders', function () {
|
||||
let p = new SCSSParser();
|
||||
parseSelector(p, '%o1 { e1 { } }', 'e1', '{%o1{…{e1}}}');
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue