├── .npmignore ├── .gitignore ├── src ├── index.ts ├── snippets.ts ├── README.md ├── eslint.ts └── javascript.ts ├── .github └── workflows │ └── dispatch.yml ├── pull_request_template.md ├── LICENSE ├── package.json ├── CHANGELOG.md ├── test ├── test-syntax.ts └── test-indent.ts ├── dist ├── index.d.ts ├── index.js └── index.cjs └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /test 3 | /node_modules 4 | .tern-* 5 | rollup.config.js 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json 3 | /dist 4 | /test/*.js 5 | /test/*.d.ts 6 | /test/*.d.ts.map 7 | .tern-* 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {javascriptLanguage, typescriptLanguage, jsxLanguage, tsxLanguage, autoCloseTags, javascript} from "./javascript" 2 | export {snippets} from "./snippets" 3 | export {esLint} from "./eslint" 4 | -------------------------------------------------------------------------------- /.github/workflows/dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Trigger CI 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Dispatch to main repo 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Emit repository_dispatch 10 | uses: mvasigh/dispatch-action@main 11 | with: 12 | # You should create a personal access token and store it in your repository 13 | token: ${{ secrets.DISPATCH_AUTH }} 14 | repo: codemirror.next 15 | owner: codemirror 16 | event_type: push 17 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Why 2 | 3 | 7 | 8 | # What changed 9 | 10 | 16 | 17 | # Test plan 18 | 19 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018-2021 by Marijn Haverbeke and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/snippets.ts: -------------------------------------------------------------------------------- 1 | import {Completion, snippetCompletion as snip} from "@codemirror/autocomplete" 2 | 3 | /// A collection of JavaScript-related 4 | /// [snippets](#autocomplete.snippet). 5 | export const snippets: readonly Completion[] = [ 6 | snip("function ${name}(${params}) {\n\t${}\n}", { 7 | label: "function", 8 | detail: "definition", 9 | type: "keyword" 10 | }), 11 | snip("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n\t${}\n}", { 12 | label: "for", 13 | detail: "loop", 14 | type: "keyword" 15 | }), 16 | snip("for (let ${name} of ${collection}) {\n\t${}\n}", { 17 | label: "for", 18 | detail: "of loop", 19 | type: "keyword" 20 | }), 21 | snip("try {\n\t${}\n} catch (${error}) {\n\t${}\n}", { 22 | label: "try", 23 | detail: "block", 24 | type: "keyword" 25 | }), 26 | snip("class ${name} {\n\tconstructor(${params}) {\n\t\t${}\n\t}\n}", { 27 | label: "class", 28 | detail: "definition", 29 | type: "keyword" 30 | }), 31 | snip("import {${names}} from \"${module}\"\n${}", { 32 | label: "import", 33 | detail: "named", 34 | type: "keyword" 35 | }), 36 | snip("import ${name} from \"${module}\"\n${}", { 37 | label: "import", 38 | detail: "default", 39 | type: "keyword" 40 | }) 41 | ] 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codemirror/lang-javascript", 3 | "version": "0.19.7", 4 | "description": "JavaScript language support for the CodeMirror code editor", 5 | "scripts": { 6 | "test": "cm-runtests", 7 | "prepare": "cm-buildhelper src/index.ts" 8 | }, 9 | "keywords": [ 10 | "editor", 11 | "code" 12 | ], 13 | "author": { 14 | "name": "Marijn Haverbeke", 15 | "email": "marijnh@gmail.com", 16 | "url": "http://marijnhaverbeke.nl" 17 | }, 18 | "type": "module", 19 | "main": "dist/index.cjs", 20 | "exports": { 21 | "import": "./dist/index.js", 22 | "require": "./dist/index.cjs" 23 | }, 24 | "types": "dist/index.d.ts", 25 | "module": "dist/index.js", 26 | "sideEffects": false, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@codemirror/autocomplete": "^0.19.0", 30 | "@codemirror/highlight": "^0.19.7", 31 | "@codemirror/language": "^0.19.0", 32 | "@codemirror/lint": "^0.19.0", 33 | "@codemirror/state": "^0.19.0", 34 | "@codemirror/view": "^0.19.0", 35 | "@lezer/javascript": "^0.15.1" 36 | }, 37 | "devDependencies": { 38 | "@codemirror/buildhelper": "^0.1.5", 39 | "@lezer/lr": "^0.15.0" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/codemirror/lang-javascript.git" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-javascript [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-javascript.svg)](https://www.npmjs.org/package/@codemirror/lang-javascript) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/6/) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-javascript/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements JavaScript language support for the 8 | [CodeMirror](https://codemirror.net/6/) code editor. 9 | 10 | The [project page](https://codemirror.net/6/) has more information, a 11 | number of [examples](https://codemirror.net/6/examples/) and the 12 | [documentation](https://codemirror.net/6/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-javascript/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## API Reference 23 | 24 | @javascript 25 | 26 | @javascriptLanguage 27 | @typescriptLanguage 28 | @jsxLanguage 29 | @tsxLanguage 30 | @autoCloseTags 31 | 32 | @snippets 33 | 34 | @esLint 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.19.7 (2022-01-28) 2 | 3 | ## 0.19.6 (2022-01-11) 4 | 5 | ### Bug fixes 6 | 7 | Remove accidentally released unfinished changes. 8 | 9 | ## 0.19.5 (2022-01-11) 10 | 11 | ### Bug fixes 12 | 13 | Add the `function` highlight modifier to variables used in tagged template expressions. 14 | 15 | ## 0.19.4 (2022-01-03) 16 | 17 | ### Bug fixes 18 | 19 | Fix highlighting of TypeScript private/public/protected keywords. 20 | 21 | ## 0.19.3 (2021-11-12) 22 | 23 | ### Bug fixes 24 | 25 | Add styling for private properties. 26 | 27 | ## 0.19.2 (2021-09-23) 28 | 29 | ### New features 30 | 31 | Use more specific highlighting tags for JSX attribute names and values. 32 | 33 | ## 0.19.1 (2021-08-11) 34 | 35 | ### Bug fixes 36 | 37 | Fix incorrect versions for @lezer dependencies. 38 | 39 | ## 0.19.0 (2021-08-11) 40 | 41 | ### Breaking changes 42 | 43 | Update dependencies to 0.19.0 44 | 45 | ## 0.18.0 (2021-03-03) 46 | 47 | ### Bug fixes 48 | 49 | Extend `indentOnInput` expression to cover closing JSX tags. 50 | 51 | ## 0.17.2 (2021-02-15) 52 | 53 | ### Bug fixes 54 | 55 | Improve highlighting tag specificity of defined function and class names. Add indentation information for JSX constructs 56 | 57 | Support smart indent for JSX syntax. 58 | 59 | ## 0.17.1 (2021-01-06) 60 | 61 | ### New features 62 | 63 | The package now also exports a CommonJS module. 64 | 65 | ## 0.17.0 (2020-12-29) 66 | 67 | ### Breaking changes 68 | 69 | First numbered release. 70 | 71 | -------------------------------------------------------------------------------- /test/test-syntax.ts: -------------------------------------------------------------------------------- 1 | import ist from "ist" 2 | import {EditorState} from "@codemirror/state" 3 | import {javascriptLanguage} from "@codemirror/lang-javascript" 4 | import {ensureSyntaxTree} from "@codemirror/language" 5 | import {Tree} from "@lezer/common" 6 | 7 | function s(doc: string) { 8 | return EditorState.create({doc, extensions: [javascriptLanguage.extension]}) 9 | } 10 | 11 | function tr(state: EditorState) { 12 | return ensureSyntaxTree(state, state.doc.length, 1e9)! 13 | } 14 | 15 | describe("javascript syntax queries", () => { 16 | it("returns a tree", () => { 17 | let state = s("let state = s()"), tree = tr(state) 18 | ist(tree instanceof Tree) 19 | ist(tree.type.name, "Script") 20 | ist(tree.length, state.doc.length) 21 | let def = tree.resolve(6) 22 | ist(def.name, "VariableDefinition") 23 | ist(def.from, 4) 24 | ist(def.to, 9) 25 | }) 26 | 27 | it("keeps the tree up to date through changes", () => { 28 | let state = s("if (2)\n x") 29 | ist(tr(state).topNode.childAfter(0)!.name, "IfStatement") 30 | state = state.update({changes: {from: 0, to: 3, insert: "fac"}}).state 31 | ist(tr(state).topNode.childAfter(0)!.name, "ExpressionStatement") 32 | }) 33 | 34 | it("reuses nodes when parsing big documents", () => { 35 | let state = s("'hello';\n" + "blah;\n".repeat(3000)) 36 | let buf = (tr(state).resolve(2) as any).buffer 37 | state = state.update({changes: {from: 2000, to: 2020, insert: "xyz"}}).state 38 | ist((tr(state).resolve(2) as any).buffer, buf) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { LRLanguage, LanguageSupport } from '@codemirror/language'; 2 | import { Completion } from '@codemirror/autocomplete'; 3 | import { Diagnostic } from '@codemirror/lint'; 4 | import { EditorView } from '@codemirror/view'; 5 | 6 | /** 7 | A language provider based on the [Lezer JavaScript 8 | parser](https://github.com/lezer-parser/javascript), extended with 9 | highlighting and indentation information. 10 | */ 11 | declare const javascriptLanguage: LRLanguage; 12 | /** 13 | A language provider for TypeScript. 14 | */ 15 | declare const typescriptLanguage: LRLanguage; 16 | /** 17 | Language provider for JSX. 18 | */ 19 | declare const jsxLanguage: LRLanguage; 20 | /** 21 | Language provider for JSX + TypeScript. 22 | */ 23 | declare const tsxLanguage: LRLanguage; 24 | /** 25 | JavaScript support. Includes [snippet](https://codemirror.net/6/docs/ref/#lang-javascript.snippets) 26 | completion. 27 | */ 28 | declare function javascript(config?: { 29 | jsx?: boolean; 30 | typescript?: boolean; 31 | }): LanguageSupport; 32 | 33 | /** 34 | A collection of JavaScript-related 35 | [snippets](https://codemirror.net/6/docs/ref/#autocomplete.snippet). 36 | */ 37 | declare const snippets: readonly Completion[]; 38 | 39 | /** 40 | Connects an [ESLint](https://eslint.org/) linter to CodeMirror's 41 | [lint](https://codemirror.net/6/docs/ref/#lint) integration. `eslint` should be an instance of the 42 | [`Linter`](https://eslint.org/docs/developer-guide/nodejs-api#linter) 43 | class, and `config` an optional ESLint configuration. The return 44 | value of this function can be passed to [`linter`](https://codemirror.net/6/docs/ref/#lint.linter) 45 | to create a JavaScript linting extension. 46 | 47 | Note that ESLint targets node, and is tricky to run in the 48 | browser. The [eslint4b](https://github.com/mysticatea/eslint4b) 49 | and 50 | [eslint4b-prebuilt](https://github.com/marijnh/eslint4b-prebuilt/) 51 | packages may help with that. 52 | */ 53 | declare function esLint(eslint: any, config?: any): (view: EditorView) => Diagnostic[]; 54 | 55 | export { esLint, javascript, javascriptLanguage, jsxLanguage, snippets, tsxLanguage, typescriptLanguage }; 56 | -------------------------------------------------------------------------------- /src/eslint.ts: -------------------------------------------------------------------------------- 1 | import {Diagnostic} from "@codemirror/lint" 2 | import {Text} from "@codemirror/state" 3 | import {EditorView} from "@codemirror/view" 4 | import {javascriptLanguage} from "./javascript" 5 | 6 | /// Connects an [ESLint](https://eslint.org/) linter to CodeMirror's 7 | /// [lint](#lint) integration. `eslint` should be an instance of the 8 | /// [`Linter`](https://eslint.org/docs/developer-guide/nodejs-api#linter) 9 | /// class, and `config` an optional ESLint configuration. The return 10 | /// value of this function can be passed to [`linter`](#lint.linter) 11 | /// to create a JavaScript linting extension. 12 | /// 13 | /// Note that ESLint targets node, and is tricky to run in the 14 | /// browser. The [eslint4b](https://github.com/mysticatea/eslint4b) 15 | /// and 16 | /// [eslint4b-prebuilt](https://github.com/marijnh/eslint4b-prebuilt/) 17 | /// packages may help with that. 18 | export function esLint(eslint: any, config?: any) { 19 | if (!config) { 20 | config = { 21 | parserOptions: {ecmaVersion: 2019, sourceType: "module"}, 22 | env: {browser: true, node: true, es6: true, es2015: true, es2017: true, es2020: true}, 23 | rules: {} 24 | } 25 | eslint.getRules().forEach((desc: any, name: string) => { 26 | if (desc.meta.docs.recommended) config.rules[name] = 2 27 | }) 28 | } 29 | 30 | return (view: EditorView) => { 31 | let {state} = view, found: Diagnostic[] = [] 32 | for (let {from, to} of javascriptLanguage.findRegions(state)) { 33 | let fromLine = state.doc.lineAt(from), offset = {line: fromLine.number - 1, col: from - fromLine.from, pos: from} 34 | for (let d of eslint.verify(state.sliceDoc(from, to), config)) 35 | found.push(translateDiagnostic(d, state.doc, offset)) 36 | } 37 | return found 38 | } 39 | } 40 | 41 | function mapPos(line: number, col: number, doc: Text, offset: {line: number, col: number, pos: number}) { 42 | return doc.line(line + offset.line).from + col + (line == 1 ? offset.col - 1 : -1) 43 | } 44 | 45 | function translateDiagnostic(input: any, doc: Text, offset: {line: number, col: number, pos: number}): Diagnostic { 46 | let start = mapPos(input.line, input.column, doc, offset) 47 | let result: Diagnostic = { 48 | from: start, 49 | to: input.endLine != null && input.endColumn != 1 ? mapPos(input.endLine, input.endColumn, doc, offset) : start, 50 | message: input.message, 51 | source: input.ruleId ? "jshint:" + input.ruleId : "jshint", 52 | severity: input.severity == 1 ? "warning" : "error", 53 | } 54 | if (input.fix) { 55 | let {range, text} = input.fix, from = range[0] + offset.pos - start, to = range[1] + offset.pos - start 56 | result.actions = [{ 57 | name: "fix", 58 | apply(view: EditorView, start: number) { 59 | view.dispatch({changes: {from: start + from, to: start + to, insert: text}, scrollIntoView: true}) 60 | } 61 | }] 62 | } 63 | return result 64 | } 65 | -------------------------------------------------------------------------------- /test/test-indent.ts: -------------------------------------------------------------------------------- 1 | import ist from "ist" 2 | import {EditorState} from "@codemirror/state" 3 | import {getIndentation} from "@codemirror/language" 4 | import {javascript} from "@codemirror/lang-javascript" 5 | 6 | function check(code: string, options: any = {}) { 7 | return () => { 8 | code = /^\n*([^]*)/.exec(code)![1] 9 | let state = EditorState.create({doc: code, extensions: [javascript(options).language]}) 10 | for (let pos = 0, lines = code.split("\n"), i = 0; i < lines.length; i++) { 11 | let line = lines[i], indent = /^\s*/.exec(line)![0].length 12 | ist(`${getIndentation(state, pos)} (${i + 1})`, `${indent} (${i + 1})`) 13 | pos += line.length + 1 14 | } 15 | } 16 | } 17 | 18 | describe("javascript indentation", () => { 19 | it("indents argument blocks", check(` 20 | foo({ 21 | bar, 22 | baz 23 | }) 24 | `)) 25 | 26 | it("indents function args", check(` 27 | foo( 28 | bar 29 | )`)) 30 | 31 | it("indents nested calls", check(` 32 | one( 33 | two( 34 | three( 35 | four() 36 | ) 37 | ) 38 | )`)) 39 | 40 | it("aligns lists", check(` 41 | one(two, 42 | three({four: five, 43 | six: seven 44 | }))`)) 45 | 46 | it("indents unfinished nodes", check(` 47 | if (foo && 48 | `)) 49 | 50 | it("deindents else", check(` 51 | if (1) 52 | a 53 | else 54 | b 55 | `)) 56 | 57 | it("support multiple opening calls on a line", check(` 58 | foo(bar(baz( 59 | ugh(quux( 60 | blah))))) 61 | `)) 62 | 63 | it("supports opening brackets on their own line", check(` 64 | const something = 65 | [ 66 | a, 67 | b 68 | ] 69 | `)) 70 | 71 | it("handles hanging braces", check(` 72 | function foo() 73 | { 74 | body() 75 | } 76 | `)) 77 | 78 | it("indents case bodies", check(` 79 | switch (1) { 80 | case 22: 81 | console.log(2) 82 | break 83 | default: 84 | return 2 85 | }`)) 86 | 87 | it("indents method chains", check(` 88 | return blah 89 | .something() 90 | .anotherOne( 91 | withArguments 92 | ) 93 | .catch(x) 94 | `)) 95 | 96 | it("indents JSON-style", check(` 97 | let j = { 98 | foo: [ 99 | { 100 | 1: true, 101 | 2: false 102 | }, 103 | {}, 104 | ], 105 | quux: null 106 | } 107 | `)) 108 | 109 | it("indents continued properties", check(` 110 | let o = { 111 | foo: 1 + 3 + 112 | 4, 113 | bar: 11 114 | } 115 | `)) 116 | 117 | it("doesn't get confused by continued param lists", check(` 118 | function foo(a, b, 119 | c, d) { 120 | return 121 | } 122 | `)) 123 | 124 | it("doesn't indent below labels", check(` 125 | abc: 126 | foo() 127 | `)) 128 | 129 | it("properly indents function expression arguments", check(` 130 | foo(100, function() { 131 | return 2 132 | }) 133 | `)) 134 | 135 | it("indents arrow functions", check(` 136 | let x = a => { 137 | return 4 138 | } 139 | let y = a => 140 | 6 141 | let x = (a, 142 | b) => 143 | 6 144 | `)) 145 | 146 | it("indents braceless structure", check(` 147 | for (;;) 148 | if (0) 149 | if (1) 150 | foo() 151 | else 152 | bar() 153 | else 154 | baz() 155 | `)) 156 | 157 | it("indents JSX constructs", check(` 158 | let y = 159 |
161 | What? 162 |
163 | 166 | `, {jsx: true})) 167 | }) 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-javascript [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-javascript.svg)](https://www.npmjs.org/package/@codemirror/lang-javascript) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/6/) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-javascript/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements JavaScript language support for the 8 | [CodeMirror](https://codemirror.net/6/) code editor. 9 | 10 | The [project page](https://codemirror.net/6/) has more information, a 11 | number of [examples](https://codemirror.net/6/examples/) and the 12 | [documentation](https://codemirror.net/6/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-javascript/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## API Reference 23 |
24 |
25 | javascript(config⁠?: {jsx⁠?: boolean, typescript⁠?: boolean} = {}) → LanguageSupport
26 | 27 |

JavaScript support. Includes snippet 28 | completion.

29 |
30 |
31 | javascriptLanguage: LezerLanguage
32 | 33 |

A language provider based on the Lezer JavaScript 34 | parser, extended with 35 | highlighting and indentation information.

36 |
37 |
38 | typescriptLanguage: LezerLanguage
39 | 40 |

A language provider for TypeScript.

41 |
42 |
43 | jsxLanguage: LezerLanguage
44 | 45 |

Language provider for JSX.

46 |
47 |
48 | tsxLanguage: LezerLanguage
49 | 50 |

Language provider for JSX + TypeScript.

51 |
52 |
53 | snippets: readonly Completion[]
54 | 55 |

A collection of JavaScript-related 56 | snippets.

57 |
58 |
59 | esLint(eslint: any, config⁠?: any) → fn(viewEditorView) → Diagnostic[]
60 | 61 |

Connects an ESLint linter to CodeMirror's 62 | lint integration. eslint should be an instance of the 63 | Linter 64 | class, and config an optional ESLint configuration. The return 65 | value of this function can be passed to linter 66 | to create a JavaScript linting extension.

67 |

Note that ESLint targets node, and is tricky to run in the 68 | browser. The eslint4b 69 | and 70 | eslint4b-prebuilt 71 | packages may help with that.

72 |
73 |
74 | 75 | -------------------------------------------------------------------------------- /src/javascript.ts: -------------------------------------------------------------------------------- 1 | import {parser} from "@lezer/javascript" 2 | import type {LRParser} from "@lezer/lr" 3 | import {SyntaxNode} from "@lezer/common" 4 | import {LRLanguage, LanguageSupport, 5 | delimitedIndent, flatIndent, continuedIndent, indentNodeProp, 6 | foldNodeProp, foldInside, syntaxTree} from "@codemirror/language" 7 | import {EditorSelection, Text} from "@codemirror/state" 8 | import {EditorView} from "@codemirror/view" 9 | import {styleTags, tags as t} from "@codemirror/highlight" 10 | import {completeFromList, ifNotIn} from "@codemirror/autocomplete" 11 | import {snippets} from "./snippets" 12 | import {parseMixed} from "@lezer/common"; 13 | import {cssLanguage} from "@codemirror/lang-css"; 14 | import {htmlLanguage} from "@codemirror/lang-html"; 15 | 16 | /// A language provider based on the [Lezer JavaScript 17 | /// parser](https://github.com/lezer-parser/javascript), extended with 18 | /// highlighting and indentation information. 19 | export const javascriptLanguage = LRLanguage.define({ 20 | parser: parser.configure({ 21 | props: [ 22 | indentNodeProp.add({ 23 | IfStatement: continuedIndent({except: /^\s*({|else\b)/}), 24 | TryStatement: continuedIndent({except: /^\s*({|catch\b|finally\b)/}), 25 | LabeledStatement: flatIndent, 26 | SwitchBody: context => { 27 | let after = context.textAfter, closed = /^\s*\}/.test(after), isCase = /^\s*(case|default)\b/.test(after) 28 | return context.baseIndent + (closed ? 0 : isCase ? 1 : 2) * context.unit 29 | }, 30 | Block: delimitedIndent({closing: "}"}), 31 | ArrowFunction: cx => cx.baseIndent + cx.unit, 32 | "TemplateString BlockComment": () => -1, 33 | "Statement Property": continuedIndent({except: /^{/}), 34 | JSXElement(context) { 35 | let closed = /^\s*<\//.test(context.textAfter) 36 | return context.lineIndent(context.node.from) + (closed ? 0 : context.unit) 37 | }, 38 | JSXEscape(context) { 39 | let closed = /\s*\}/.test(context.textAfter) 40 | return context.lineIndent(context.node.from) + (closed ? 0 : context.unit) 41 | }, 42 | "JSXOpenTag JSXSelfClosingTag"(context) { 43 | return context.column(context.node.from) + context.unit 44 | } 45 | }), 46 | foldNodeProp.add({ 47 | "Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression": foldInside, 48 | BlockComment(tree) { return {from: tree.from + 2, to: tree.to - 2} } 49 | }), 50 | styleTags({ 51 | "get set async static": t.modifier, 52 | "for while do if else switch try catch finally return throw break continue default case": t.controlKeyword, 53 | "in of await yield void typeof delete instanceof": t.operatorKeyword, 54 | "let var const function class extends": t.definitionKeyword, 55 | "import export from": t.moduleKeyword, 56 | "with debugger as new": t.keyword, 57 | TemplateString: t.special(t.string), 58 | Super: t.atom, 59 | BooleanLiteral: t.bool, 60 | this: t.self, 61 | null: t.null, 62 | Star: t.modifier, 63 | VariableName: t.variableName, 64 | "CallExpression/VariableName TaggedTemplateExpression/VariableName": t.function(t.variableName), 65 | VariableDefinition: t.definition(t.variableName), 66 | Label: t.labelName, 67 | PropertyName: t.propertyName, 68 | PrivatePropertyName: t.special(t.propertyName), 69 | "CallExpression/MemberExpression/PropertyName": t.function(t.propertyName), 70 | "FunctionDeclaration/VariableDefinition": t.function(t.definition(t.variableName)), 71 | "ClassDeclaration/VariableDefinition": t.definition(t.className), 72 | PropertyDefinition: t.definition(t.propertyName), 73 | PrivatePropertyDefinition: t.definition(t.special(t.propertyName)), 74 | UpdateOp: t.updateOperator, 75 | LineComment: t.lineComment, 76 | BlockComment: t.blockComment, 77 | Number: t.number, 78 | String: t.string, 79 | ArithOp: t.arithmeticOperator, 80 | LogicOp: t.logicOperator, 81 | BitOp: t.bitwiseOperator, 82 | CompareOp: t.compareOperator, 83 | RegExp: t.regexp, 84 | Equals: t.definitionOperator, 85 | "Arrow : Spread": t.punctuation, 86 | "( )": t.paren, 87 | "[ ]": t.squareBracket, 88 | "{ }": t.brace, 89 | "InterpolationStart InterpolationEnd": t.special(t.brace), 90 | ".": t.derefOperator, 91 | ", ;": t.separator, 92 | 93 | TypeName: t.typeName, 94 | TypeDefinition: t.definition(t.typeName), 95 | "type enum interface implements namespace module declare": t.definitionKeyword, 96 | "abstract global Privacy readonly override": t.modifier, 97 | "is keyof unique infer": t.operatorKeyword, 98 | 99 | JSXAttributeValue: t.attributeValue, 100 | JSXText: t.content, 101 | "JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag": t.angleBracket, 102 | "JSXIdentifier JSXNameSpacedName": t.tagName, 103 | "JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName": t.attributeName 104 | }) 105 | ], 106 | 107 | // https://discuss.codemirror.net/t/deeply-nested-parser/3485 108 | wrap: parseMixed((node, input) => { 109 | if (node.type.name !== "TemplateString") { 110 | return null; 111 | } 112 | 113 | const { parent, prevSibling: tagNode } = node.node; 114 | 115 | if (tagNode == null) return; 116 | let parser: LRParser; 117 | 118 | if ( 119 | ["VariableName", "MemberExpression", "CallExpression"].includes( 120 | tagNode.type.name 121 | ) 122 | ) { 123 | const tag = input.read(tagNode.from, tagNode.to); 124 | 125 | if ( 126 | tag === "css" || 127 | tag.startsWith("styled.") || 128 | (tag.startsWith("styled(") && tag[tag.length - 1] === ")") 129 | ) { 130 | parser = cssLanguage.parser; 131 | } else if (tag === "html") { 132 | parser = htmlLanguage.parser; 133 | } else { 134 | return null; 135 | } 136 | } else if ( 137 | parent.type.name === "JSXEscape" && 138 | parent.parent.type.name === "JSXElement" && 139 | input.read(parent.parent.from, parent.parent.from + 11) === "