flink-sql-language-server
Version:
A LSP-based language server for Apache Flink SQL
284 lines (283 loc) • 13.1 kB
JavaScript
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;
;