UNPKG

flink-sql-language-server

Version:

A LSP-based language server for Apache Flink SQL

284 lines (283 loc) 13.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isSemanticTokenReference = exports.LSPServer = void 0; const antlr4_c3_1 = require("antlr4-c3"); const antlr4ts_1 = require("antlr4ts"); const tree_1 = require("antlr4ts/tree"); const lsp = __importStar(require("vscode-languageserver")); const flinkdb_schema_json_1 = __importDefault(require("./assets/flinkdb.schema.json")); const completion_parser_1 = require("./completion-parser"); const cursor_1 = require("./cursor"); const FlinkSQLLexer_1 = require("./lib/FlinkSQLLexer"); const FlinkSQLParser_1 = require("./lib/FlinkSQLParser"); const preview_listener_1 = require("./listeners/preview.listener"); const schema_listener_1 = require("./listeners/schema.listener"); const statement_listener_1 = require("./listeners/statement.listener"); const structure_listener_1 = require("./listeners/structure.listener"); const tokens_collector_listener_1 = require("./listeners/tokens-collector.listener"); const parsing_warehouse_1 = require("./parsing-warehouse"); const protocol_translation_1 = require("./protocol-translation"); const schema_registry_1 = require("./schema-registry"); const utils_1 = require("./utils"); const lineage_visitor_1 = require("./visitors/lineage.visitor"); const space_replacer_format_visitor_1 = require("./visitors/space-replacer-format.visitor"); const preferredRules = [ FlinkSQLParser_1.FlinkSQLParser.RULE_functionName, FlinkSQLParser_1.FlinkSQLParser.RULE_tableName, FlinkSQLParser_1.FlinkSQLParser.RULE_columnName, FlinkSQLParser_1.FlinkSQLParser.RULE_viewName, FlinkSQLParser_1.FlinkSQLParser.RULE_databaseName, FlinkSQLParser_1.FlinkSQLParser.RULE_catalogName ]; const ignoredTokens = [ FlinkSQLLexer_1.FlinkSQLLexer.EOF, FlinkSQLLexer_1.FlinkSQLLexer.SEMICOLON, FlinkSQLLexer_1.FlinkSQLLexer.COMMA, FlinkSQLLexer_1.FlinkSQLLexer.DOT, FlinkSQLLexer_1.FlinkSQLLexer.ASTERISK, FlinkSQLLexer_1.FlinkSQLLexer.COLON, FlinkSQLLexer_1.FlinkSQLLexer.L_PARENTHESIS, FlinkSQLLexer_1.FlinkSQLLexer.R_PARENTHESIS, FlinkSQLLexer_1.FlinkSQLLexer.L_BRACE, FlinkSQLLexer_1.FlinkSQLLexer.R_BRACE, FlinkSQLLexer_1.FlinkSQLLexer.L_BRACKET, FlinkSQLLexer_1.FlinkSQLLexer.R_BRACKET, FlinkSQLLexer_1.FlinkSQLLexer.STRING_LITERAL, FlinkSQLLexer_1.FlinkSQLLexer.BIND_PARAMETER, FlinkSQLLexer_1.FlinkSQLLexer.REAL_LITERAL, FlinkSQLLexer_1.FlinkSQLLexer.IDENTIFIER ]; class LSPServer { constructor() { this._warehouse = new parsing_warehouse_1.ParsingWarehouse(); this._schemaRegistry = new schema_registry_1.SchemaRegistry(); } get warehouse() { return this._warehouse; } get schemaRegistry() { return this._schemaRegistry; } getTokenByPosition(document, position) { const parsingValue = this._warehouse.getParsingValue(document); const offset = document.offsetAt(position); return parsingValue.tokenStream.getTokens().find(t => t.startIndex <= offset && offset <= t.stopIndex + 1); } doValidation(document) { const parsingValue = this._warehouse.getParsingValue(document); return parsingValue.errors; } doFormatting(document, options) { const parsingValue = this._warehouse.getParsingValue(document); try { const visitor = new space_replacer_format_visitor_1.SpaceReplacerFormatVisitor(parsingValue.tokenStream, options); visitor.visit(parsingValue.tree); return visitor.textEdits; } catch (e) { return []; } } doFoldingRanges(document) { const parsingValue = this._warehouse.getParsingValue(document); const listener = new statement_listener_1.StatementListener(document); tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); const ranges = listener.getRanges(); return ranges.map(range => (0, protocol_translation_1.toFoldingRange)(range)); } doReferences(document, position, skipSelf = true) { const parsingValue = this._warehouse.getParsingValue(document); const tokens = parsingValue.tokenStream.getTokens(); const currentOffset = document.offsetAt(position); const currentToken = tokens.find(token => { return token.startIndex <= currentOffset && currentOffset <= token.stopIndex; }); if (currentToken?.text === undefined) { return []; } const listener = new tokens_collector_listener_1.TokensCollectorListener(document); tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); const semanticTokens = listener.getSemanticTokens(); const references = []; const compareText = (str1, str2) => (0, utils_1.sanitizeText)(str1).localeCompare((0, utils_1.sanitizeText)(str2)) === 0; const findReferences = (token) => { if (currentToken.text && compareText(currentToken.text, token.name)) { references.push(token.range); } }; semanticTokens.forEach(findReferences); if (skipSelf) { return references.filter(range => !(0, protocol_translation_1.positionInRange)(document, position, range)); } return references; } doCompletion(document, position) { const sql = cursor_1.defaultCursor.insertAt(document.getText(), position); const charStream = antlr4ts_1.CharStreams.fromString(sql); const lexer = new FlinkSQLLexer_1.FlinkSQLLexer(charStream); const tokenStream = new antlr4ts_1.CommonTokenStream(lexer); const parser = new FlinkSQLParser_1.FlinkSQLParser(tokenStream); parser.buildParseTree = true; parser.removeErrorListeners(); const tree = parser.program(); const completionItems = []; const sqlCompletionParser = new completion_parser_1.FlinkSQLCompletionParser(tree, cursor_1.defaultCursor); const suggestions = sqlCompletionParser.getSuggestions({ getCatalogs: () => { return this.schemaRegistry.getSchemaCatalogs(); }, getDatabases: args => { return this.schemaRegistry.getSchemaDatabases(args); }, getTables: args => { return this.schemaRegistry.getSchemaTables(args); }, getColumns: args => { return this.schemaRegistry.getSchemaColumns(args); } }); completionItems.push(...suggestions); const parsingValue = this._warehouse.getParsingValue(document); const offset = document.offsetAt(position); const token = parsingValue.tokenStream.getTokens().find(t => t.startIndex <= offset && offset <= t.stopIndex + 1); if (!token) { return { completionItems: [], identifiers: [] }; } const core = new antlr4_c3_1.CodeCompletionCore(parsingValue.parser); core.ignoredTokens = new Set(ignoredTokens); core.preferredRules = new Set(preferredRules); let index = token.tokenIndex; if (token.type === FlinkSQLLexer_1.FlinkSQLLexer.DOT) { index += 1; } const candidates = core.collectCandidates(index); const identifiers = []; if (candidates.rules.size > 0) { if (candidates.rules.has(FlinkSQLParser_1.FlinkSQLParser.RULE_functionName)) { completionItems.push(...flinkdb_schema_json_1.default.functions.map(f => (0, protocol_translation_1.toCompletionItem)('built-in', f.name, f.name, 'built-in function', f.description))); } } for (const candidate of Array.from(candidates.tokens)) { const keyword = parsingValue.parser.vocabulary.getDisplayName(candidate[0]).replace(/'/g, ''); completionItems.push({ kind: lsp.CompletionItemKind.Keyword, label: keyword, insertText: keyword, insertTextFormat: lsp.InsertTextFormat.Snippet }); } return { completionItems, identifiers }; } parseStructure(document, options = { ignoreInsertSource: false, addCreateContext: false }) { const parsingValue = this._warehouse.getParsingValue(document); const listener = new structure_listener_1.FlinkStructureListener(document); tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); let nodes = listener.getNodes(); let edges = listener.getEdges(); if (options.ignoreInsertSource) { const tmp = (0, structure_listener_1.ignoreInsertSource)(nodes, edges); nodes = tmp.nodes; edges = tmp.edges; } if (options.addCreateContext) { const tmp = (0, structure_listener_1.addFromCreateContext)(nodes, edges); nodes = tmp.nodes; edges = tmp.edges; } return { nodes, edges }; } generatePreviewScript(document, context, range) { const parsingValue = range ? this._warehouse.parseDocument(document, range) : this._warehouse.getParsingValue(document); if (parsingValue.errors.length) { console.error(`Document '${document.uri}' contains syntax errors, preview script failed to generate.`); return document.getText(); } const listener = new preview_listener_1.PreviewListener(parsingValue.origin, context); let previewScript = parsingValue.origin; try { tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); previewScript = listener.previewScript; } catch (e) { console.error(e); } return (0, preview_listener_1.appendCreateNonTemporaryTables)(previewScript, context); } extractSchemaContexts(document, range) { const parsingValue = range ? this._warehouse.parseDocument(document, range) : this._warehouse.getParsingValue(document); const listener = new schema_listener_1.SchemaListener(); try { tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); return listener.getSchemaContexts(); } catch (e) { console.error(e); return []; } } collectTokens(document, range) { const parsingValue = range ? this._warehouse.parseDocument(document, range) : this._warehouse.getParsingValue(document); if (parsingValue.tokenCollection) { return parsingValue.tokenCollection; } const listener = new tokens_collector_listener_1.TokensCollectorListener(document); tree_1.ParseTreeWalker.DEFAULT.walk(listener, parsingValue.tree); if (!range) { this._warehouse.setTokenCollection(document, { semanticTokens: listener.getSemanticTokens(), stringTokens: listener.getStringTokens() }); } return { semanticTokens: listener.getSemanticTokens(), stringTokens: listener.getStringTokens() }; } getLineage(document, options = {}) { const parsingValue = this._warehouse.getParsingValue(document); const visitor = new lineage_visitor_1.LineageVisitor(options); try { parsingValue.tree.accept(visitor); return visitor.getLineage(); } catch (e) { console.error(`Language Server: Lineage visitor transversal failed.\n\n${e}`); return { nodes: [], edges: [] }; } } } exports.LSPServer = LSPServer; function isSemanticTokenReference(token1, token2) { return (token1.name.replace(/['"`]/g, '').localeCompare(token2.name.replace(/['"`]/g, '')) === 0 && token1.type === token2.type); } exports.isSemanticTokenReference = isSemanticTokenReference;