'use strict'; var javascript$1 = require('@lezer/javascript'); var language = require('@codemirror/language'); var state = require('@codemirror/state'); var view = require('@codemirror/view'); var autocomplete = require('@codemirror/autocomplete'); var common = require('@lezer/common'); /** A collection of JavaScript-related [snippets](https://codemirror.net/6/docs/ref/#autocomplete.snippet). */ const snippets = [ autocomplete.snippetCompletion("function ${name}(${params}) {\n\t${}\n}", { label: "function", detail: "definition", type: "keyword" }), autocomplete.snippetCompletion("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n\t${}\n}", { label: "for", detail: "loop", type: "keyword" }), autocomplete.snippetCompletion("for (let ${name} of ${collection}) {\n\t${}\n}", { label: "for", detail: "of loop", type: "keyword" }), autocomplete.snippetCompletion("do {\n\t${}\n} while (${})", { label: "do", detail: "loop", type: "keyword" }), autocomplete.snippetCompletion("while (${}) {\n\t${}\n}", { label: "while", detail: "loop", type: "keyword" }), autocomplete.snippetCompletion("try {\n\t${}\n} catch (${error}) {\n\t${}\n}", { label: "try", detail: "/ catch block", type: "keyword" }), autocomplete.snippetCompletion("if (${}) {\n\t${}\n}", { label: "if", detail: "block", type: "keyword" }), autocomplete.snippetCompletion("if (${}) {\n\t${}\n} else {\n\t${}\n}", { label: "if", detail: "/ else block", type: "keyword" }), autocomplete.snippetCompletion("class ${name} {\n\tconstructor(${params}) {\n\t\t${}\n\t}\n}", { label: "class", detail: "definition", type: "keyword" }), autocomplete.snippetCompletion("import {${names}} from \"${module}\"\n${}", { label: "import", detail: "named", type: "keyword" }), autocomplete.snippetCompletion("import ${name} from \"${module}\"\n${}", { label: "import", detail: "default", type: "keyword" }) ]; /** A collection of snippet completions for TypeScript. Includes the JavaScript [snippets](https://codemirror.net/6/docs/ref/#lang-javascript.snippets). */ const typescriptSnippets = snippets.concat([ autocomplete.snippetCompletion("interface ${name} {\n\t${}\n}", { label: "interface", detail: "definition", type: "keyword" }), autocomplete.snippetCompletion("type ${name} = ${type}", { label: "type", detail: "definition", type: "keyword" }), autocomplete.snippetCompletion("enum ${name} {\n\t${}\n}", { label: "enum", detail: "definition", type: "keyword" }) ]); const cache = new common.NodeWeakMap(); const ScopeNodes = new Set([ "Script", "Block", "FunctionExpression", "FunctionDeclaration", "ArrowFunction", "MethodDeclaration", "ForStatement" ]); function defID(type) { return (node, def) => { let id = node.node.getChild("VariableDefinition"); if (id) def(id, type); return true; }; } const functionContext = ["FunctionDeclaration"]; const gatherCompletions = { FunctionDeclaration: defID("function"), ClassDeclaration: defID("class"), ClassExpression: () => true, EnumDeclaration: defID("constant"), TypeAliasDeclaration: defID("type"), NamespaceDeclaration: defID("namespace"), VariableDefinition(node, def) { if (!node.matchContext(functionContext)) def(node, "variable"); }, TypeDefinition(node, def) { def(node, "type"); }, __proto__: null }; function getScope(doc, node) { let cached = cache.get(node); if (cached) return cached; let completions = [], top = true; function def(node, type) { let name = doc.sliceString(node.from, node.to); completions.push({ label: name, type }); } node.cursor(common.IterMode.IncludeAnonymous).iterate(node => { if (top) { top = false; } else if (node.name) { let gather = gatherCompletions[node.name]; if (gather && gather(node, def) || ScopeNodes.has(node.name)) return false; } else if (node.to - node.from > 8192) { // Allow caching for bigger internal nodes for (let c of getScope(doc, node.node)) completions.push(c); return false; } }); cache.set(node, completions); return completions; } const Identifier = /^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/; const dontComplete = [ "TemplateString", "String", "RegExp", "LineComment", "BlockComment", "VariableDefinition", "TypeDefinition", "Label", "PropertyDefinition", "PropertyName", "PrivatePropertyDefinition", "PrivatePropertyName", ".", "?." ]; /** Completion source that looks up locally defined names in JavaScript code. */ function localCompletionSource(context) { let inner = language.syntaxTree(context.state).resolveInner(context.pos, -1); if (dontComplete.indexOf(inner.name) > -1) return null; let isWord = inner.name == "VariableName" || inner.to - inner.from < 20 && Identifier.test(context.state.sliceDoc(inner.from, inner.to)); if (!isWord && !context.explicit) return null; let options = []; for (let pos = inner; pos; pos = pos.parent) { if (ScopeNodes.has(pos.name)) options = options.concat(getScope(context.state.doc, pos)); } return { options, from: isWord ? inner.from : context.pos, validFor: Identifier }; } function pathFor(read, member, name) { var _a; let path = []; for (;;) { let obj = member.firstChild, prop; if ((obj === null || obj === void 0 ? void 0 : obj.name) == "VariableName") { path.push(read(obj)); return { path: path.reverse(), name }; } else if ((obj === null || obj === void 0 ? void 0 : obj.name) == "MemberExpression" && ((_a = (prop = obj.lastChild)) === null || _a === void 0 ? void 0 : _a.name) == "PropertyName") { path.push(read(prop)); member = obj; } else { return null; } } } /** Helper function for defining JavaScript completion sources. It returns the completable name and object path for a completion context, or null if no name/property completion should happen at that position. For example, when completing after `a.b.c` it will return `{path: ["a", "b"], name: "c"}`. When completing after `x` it will return `{path: [], name: "x"}`. When not in a property or name, it will return null if `context.explicit` is false, and `{path: [], name: ""}` otherwise. */ function completionPath(context) { let read = (node) => context.state.doc.sliceString(node.from, node.to); let inner = language.syntaxTree(context.state).resolveInner(context.pos, -1); if (inner.name == "PropertyName") { return pathFor(read, inner.parent, read(inner)); } else if ((inner.name == "." || inner.name == "?.") && inner.parent.name == "MemberExpression") { return pathFor(read, inner.parent, ""); } else if (dontComplete.indexOf(inner.name) > -1) { return null; } else if (inner.name == "VariableName" || inner.to - inner.from < 20 && Identifier.test(read(inner))) { return { path: [], name: read(inner) }; } else if (inner.name == "MemberExpression") { return pathFor(read, inner, ""); } else { return context.explicit ? { path: [], name: "" } : null; } } function enumeratePropertyCompletions(obj, top) { let options = [], seen = new Set; for (let depth = 0;; depth++) { for (let name of (Object.getOwnPropertyNames || Object.keys)(obj)) { if (!/^[a-zA-Z_$\xaa-\uffdc][\w$\xaa-\uffdc]*$/.test(name) || seen.has(name)) continue; seen.add(name); let value; try { value = obj[name]; } catch (_) { continue; } options.push({ label: name, type: typeof value == "function" ? (/^[A-Z]/.test(name) ? "class" : top ? "function" : "method") : top ? "variable" : "property", boost: -depth }); } let next = Object.getPrototypeOf(obj); if (!next) return options; obj = next; } } /** Defines a [completion source](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource) that completes from the given scope object (for example `globalThis`). Will enter properties of the object when completing properties on a directly-named path. */ function scopeCompletionSource(scope) { let cache = new Map; return (context) => { let path = completionPath(context); if (!path) return null; let target = scope; for (let step of path.path) { target = target[step]; if (!target) return null; } let options = cache.get(target); if (!options) cache.set(target, options = enumeratePropertyCompletions(target, !path.path.length)); return { from: context.pos - path.name.length, options, validFor: Identifier }; }; } /** A language provider based on the [Lezer JavaScript parser](https://github.com/lezer-parser/javascript), extended with highlighting and indentation information. */ const javascriptLanguage = language.LRLanguage.define({ name: "javascript", parser: javascript$1.parser.configure({ props: [ language.indentNodeProp.add({ IfStatement: language.continuedIndent({ except: /^\s*({|else\b)/ }), TryStatement: language.continuedIndent({ except: /^\s*({|catch\b|finally\b)/ }), LabeledStatement: language.flatIndent, SwitchBody: context => { let after = context.textAfter, closed = /^\s*\}/.test(after), isCase = /^\s*(case|default)\b/.test(after); return context.baseIndent + (closed ? 0 : isCase ? 1 : 2) * context.unit; }, Block: language.delimitedIndent({ closing: "}" }), ArrowFunction: cx => cx.baseIndent + cx.unit, "TemplateString BlockComment": () => null, "Statement Property": language.continuedIndent({ except: /^{/ }), JSXElement(context) { let closed = /^\s*<\//.test(context.textAfter); return context.lineIndent(context.node.from) + (closed ? 0 : context.unit); }, JSXEscape(context) { let closed = /\s*\}/.test(context.textAfter); return context.lineIndent(context.node.from) + (closed ? 0 : context.unit); }, "JSXOpenTag JSXSelfClosingTag"(context) { return context.column(context.node.from) + context.unit; } }), language.foldNodeProp.add({ "Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType": language.foldInside, BlockComment(tree) { return { from: tree.from + 2, to: tree.to - 2 }; } }) ] }), languageData: { closeBrackets: { brackets: ["(", "[", "{", "'", '"', "`"] }, commentTokens: { line: "//", block: { open: "/*", close: "*/" } }, indentOnInput: /^\s*(?:case |default:|\{|\}|<\/)$/, wordChars: "$" } }); const jsxSublanguage = { test: node => /^JSX/.test(node.name), facet: language.defineLanguageFacet({ commentTokens: { block: { open: "{/*", close: "*/}" } } }) }; /** A language provider for TypeScript. */ const typescriptLanguage = javascriptLanguage.configure({ dialect: "ts" }, "typescript"); /** Language provider for JSX. */ const jsxLanguage = javascriptLanguage.configure({ dialect: "jsx", props: [language.sublanguageProp.add(n => n.isTop ? [jsxSublanguage] : undefined)] }); /** Language provider for JSX + TypeScript. */ const tsxLanguage = javascriptLanguage.configure({ dialect: "jsx ts", props: [language.sublanguageProp.add(n => n.isTop ? [jsxSublanguage] : undefined)] }, "typescript"); let kwCompletion = (name) => ({ label: name, type: "keyword" }); const keywords = "break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(kwCompletion); const typescriptKeywords = keywords.concat(["declare", "implements", "private", "protected", "public"].map(kwCompletion)); /** JavaScript support. Includes [snippet](https://codemirror.net/6/docs/ref/#lang-javascript.snippets) and local variable completion. */ function javascript(config = {}) { let lang = config.jsx ? (config.typescript ? tsxLanguage : jsxLanguage) : config.typescript ? typescriptLanguage : javascriptLanguage; let completions = config.typescript ? typescriptSnippets.concat(typescriptKeywords) : snippets.concat(keywords); return new language.LanguageSupport(lang, [ javascriptLanguage.data.of({ autocomplete: autocomplete.ifNotIn(dontComplete, autocomplete.completeFromList(completions)) }), javascriptLanguage.data.of({ autocomplete: localCompletionSource }), config.jsx ? autoCloseTags : [], ]); } function findOpenTag(node) { for (;;) { if (node.name == "JSXOpenTag" || node.name == "JSXSelfClosingTag" || node.name == "JSXFragmentTag") return node; if (node.name == "JSXEscape" || !node.parent) return null; node = node.parent; } } function elementName(doc, tree, max = doc.length) { for (let ch = tree === null || tree === void 0 ? void 0 : tree.firstChild; ch; ch = ch.nextSibling) { if (ch.name == "JSXIdentifier" || ch.name == "JSXBuiltin" || ch.name == "JSXNamespacedName" || ch.name == "JSXMemberExpression") return doc.sliceString(ch.from, Math.min(ch.to, max)); } return ""; } const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent); /** Extension that will automatically insert JSX close tags when a `>` or `/` is typed. */ const autoCloseTags = view.EditorView.inputHandler.of((view, from, to, text, defaultInsert) => { if ((android ? view.composing : view.compositionStarted) || view.state.readOnly || from != to || (text != ">" && text != "/") || !javascriptLanguage.isActiveAt(view.state, from, -1)) return false; let base = defaultInsert(), { state: state$1 } = base; let closeTags = state$1.changeByRange(range => { var _a; let { head } = range, around = language.syntaxTree(state$1).resolveInner(head - 1, -1), name; if (around.name == "JSXStartTag") around = around.parent; if (state$1.doc.sliceString(head - 1, head) != text || around.name == "JSXAttributeValue" && around.to > head) ; else if (text == ">" && around.name == "JSXFragmentTag") { return { range, changes: { from: head, insert: `` } }; } else if (text == "/" && around.name == "JSXStartCloseTag") { let empty = around.parent, base = empty.parent; if (base && empty.from == head - 2 && ((name = elementName(state$1.doc, base.firstChild, head)) || ((_a = base.firstChild) === null || _a === void 0 ? void 0 : _a.name) == "JSXFragmentTag")) { let insert = `${name}>`; return { range: state.EditorSelection.cursor(head + insert.length, -1), changes: { from: head, insert } }; } } else if (text == ">") { let openTag = findOpenTag(around); if (openTag && openTag.name == "JSXOpenTag" && !/^\/?>|^<\//.test(state$1.doc.sliceString(head, head + 2)) && (name = elementName(state$1.doc, openTag, head))) return { range, changes: { from: head, insert: `` } }; } return { range }; }); if (closeTags.changes.empty) return false; view.dispatch([ base, state$1.update(closeTags, { userEvent: "input.complete", scrollIntoView: true }) ]); return true; }); /** Connects an [ESLint](https://eslint.org/) linter to CodeMirror's [lint](https://codemirror.net/6/docs/ref/#lint) integration. `eslint` should be an instance of the [`Linter`](https://eslint.org/docs/developer-guide/nodejs-api#linter) class, and `config` an optional ESLint configuration. The return value of this function can be passed to [`linter`](https://codemirror.net/6/docs/ref/#lint.linter) to create a JavaScript linting extension. Note that ESLint targets node, and is tricky to run in the browser. The [eslint-linter-browserify](https://github.com/UziTech/eslint-linter-browserify) package may help with that (see [example](https://github.com/UziTech/eslint-linter-browserify/blob/master/example/script.js)). */ function esLint(eslint, config) { if (!config) { config = { parserOptions: { ecmaVersion: 2019, sourceType: "module" }, env: { browser: true, node: true, es6: true, es2015: true, es2017: true, es2020: true }, rules: {} }; eslint.getRules().forEach((desc, name) => { if (desc.meta.docs.recommended) config.rules[name] = 2; }); } return (view) => { let { state } = view, found = []; for (let { from, to } of javascriptLanguage.findRegions(state)) { let fromLine = state.doc.lineAt(from), offset = { line: fromLine.number - 1, col: from - fromLine.from, pos: from }; for (let d of eslint.verify(state.sliceDoc(from, to), config)) found.push(translateDiagnostic(d, state.doc, offset)); } return found; }; } function mapPos(line, col, doc, offset) { return doc.line(line + offset.line).from + col + (line == 1 ? offset.col - 1 : -1); } function translateDiagnostic(input, doc, offset) { let start = mapPos(input.line, input.column, doc, offset); let result = { from: start, to: input.endLine != null && input.endColumn != 1 ? mapPos(input.endLine, input.endColumn, doc, offset) : start, message: input.message, source: input.ruleId ? "eslint:" + input.ruleId : "eslint", severity: input.severity == 1 ? "warning" : "error", }; if (input.fix) { let { range, text } = input.fix, from = range[0] + offset.pos - start, to = range[1] + offset.pos - start; result.actions = [{ name: "fix", apply(view, start) { view.dispatch({ changes: { from: start + from, to: start + to, insert: text }, scrollIntoView: true }); } }]; } return result; } exports.autoCloseTags = autoCloseTags; exports.completionPath = completionPath; exports.esLint = esLint; exports.javascript = javascript; exports.javascriptLanguage = javascriptLanguage; exports.jsxLanguage = jsxLanguage; exports.localCompletionSource = localCompletionSource; exports.scopeCompletionSource = scopeCompletionSource; exports.snippets = snippets; exports.tsxLanguage = tsxLanguage; exports.typescriptLanguage = typescriptLanguage; exports.typescriptSnippets = typescriptSnippets;