UNPKG

@quodatum/xqlint

Version:
726 lines (651 loc) 25.4 kB
exports.Translator = function (rootStcx, ast) { 'use strict'; var Errors = require('./errors'); var StaticError = Errors.StaticError; var StaticWarning = Errors.StaticWarning; var TreeOps = require('../tree_ops').TreeOps; var StaticContext = require('./static_context').StaticContext; var Handlers = require('./handlers'); var markers = []; var addStaticError = function (e) { const marker={ pos: e.getPos(), code: e.getCode(), level: 'error', message: '[' + e.getCode() + '] ' + e.getMessage() }; markers.push(marker); }; var addWarning = function (code, message, pos) { markers.push({ pos: pos, code: code, level: 'warning', message: '[' + code + '] ' + message }); }; var get = function (node, path) { var result = []; if (path.length === 0) { return node; } node.children.forEach(function (child) { if (child.name === path[0] && path.length > 1) { result = get(child, path.slice(1)); } else if (child.name === path[0]) { result.push(child); } }); return result; }; this.apply = function (fn) { try { fn(); } catch (e) { if (e instanceof StaticError) { addStaticError(e); } else if (e instanceof StaticWarning) { addWarning(e.getCode(), e.getMessage(), e.getPos()); } else { throw e; } } }; this.getMarkers = function () { return markers; }; var translator = this; rootStcx.pos = ast.pos; var sctx = rootStcx; var pushSctx = function (pos) { sctx = new StaticContext(sctx, pos); sctx.parent.children.push(sctx); }; var popSctx = function (pos) { if (pos !== undefined) { sctx.pos.el = pos.el; sctx.pos.ec = pos.ec; } Object.keys(sctx.varRefs).forEach(function (key) { if (!sctx.variables[key]) { sctx.parent.varRefs[key] = true; } }); Object.keys(sctx.variables).forEach(function (key) { if (!sctx.varRefs[key] && sctx.variables[key].type !== 'GroupingVariable' && sctx.variables[key].type !== 'CatchVar' && sctx.variables[key].qname.name[0] !== '_') { addWarning('W03', 'Unused variable "$' + sctx.variables[key].qname.name + '"', sctx.variables[key].pos); } }); sctx = sctx.parent; }; this.visitOnly = function (node, names) { node.children.forEach(function (child) { if (names.indexOf(child.name) !== -1) { translator.visit(child); } }); }; this.getFirstChild = function (node, name) { var result; node.children.forEach(function (child) { if (child.name === name && result === undefined) { result = child; } }); return result; }; this.XQuery = function (node) { rootStcx.description = node.comment ? node.comment.description : undefined; }; this.ModuleDecl = function (node) { this.visitChildren(node, Handlers.ModuleDecl(translator, rootStcx, node)); return true; }; this.Prolog = function (node) { //Visit first part of the prolog this.visitOnly(node, ['DefaultNamespaceDecl', 'Setter', 'NamespaceDecl', 'Import']); //Get the Index of variable and function Declarations ast.index.forEach(function (node) { if (node.name === 'VarDecl') { node.children.forEach(function (child) { if (child.name === 'VarName') { translator.apply(function () { var value = TreeOps.flatten(child); var qname = rootStcx.resolveQName(value, child.pos); rootStcx.addVariable(qname, node.name, child.pos); }); } }); } else if (node.name === 'FunctionDecl') { var qname, pos, params = [], ret; node.children.forEach(function (child) { if (child.name === 'EQName') { qname = child; pos = child.pos; } else if (child.name === 'ParamList') { child.children.forEach(function (c) { if (c.name === 'Param') { params.push(TreeOps.flatten(c)); } }); } else if (child.name === 'SequenceType') { ret = TreeOps.flatten(child); } }); translator.apply(function () { qname = TreeOps.flatten(qname); qname = rootStcx.resolveQName(qname, pos); rootStcx.addFunction(qname, pos, params, ret); }); } }); //Visit second part of the prolog this.visitOnly(node, ['ContextItemDecl', 'AnnotatedDecl', 'OptionDecl']); return true; }; this.ModuleImport = function (node) { let ats = [], seenAt = false; this.visitChildren(node, { TOKEN: function (token) { seenAt = 'at' === token.value; }, URILiteral: function (node) { if (!seenAt) { return; } var url = TreeOps.flatten(node); url = url.substring(1, url.length - 1); ats.push({ url: url, pos: node.pos }); } }); this.visitChildren(node, Handlers.ModuleImport(translator, rootStcx, node, ats)); return true; }; this.SchemaImport = function (node) { this.visitChildren(node, Handlers.SchemaImport(translator, rootStcx, node)); return true; }; this.DefaultNamespaceDecl = function (node) { this.visitChildren(node, Handlers.DefaultNamespaceDecl(translator, rootStcx, node)); return true; }; this.NamespaceDecl = function (node) { this.visitChildren(node, Handlers.NamespaceDecl(translator, rootStcx, node)); return true; }; var annotations = {}; this.AnnotatedDecl = function (node) { annotations = {}; this.visitChildren(node, Handlers.NamespaceDecl(translator, rootStcx, node)); return true; }; this.CompatibilityAnnotation = function () { annotations['http://www.w3.org/2012/xquery#updating'] = []; return true; }; this.Annotation = function (node) { this.visitChildren(node, { EQName: function (eqname) { var value = TreeOps.flatten(eqname); translator.apply(function () { var qname = sctx.resolveQName(value, eqname.pos); annotations[qname.uri + '#' + qname.name] = []; }); } }); return true; }; this.VarDecl = function (node) { //var typeDecl = get(node, ['TypeDeclaration']); //if(!typeDecl){ // addWarning('W05', 'Untyped module variable', node.pos); //} try { var varname = translator.getFirstChild(node, 'VarName'); var value = TreeOps.flatten(varname); var qname = sctx.resolveQName(value, varname.pos); var variable = rootStcx.getVariable(qname); if (variable) { variable.annotations = annotations; variable.type = TreeOps.flatten(get(node, ['TypeDeclaration'])[0]).substring(2).trim(); var last = variable.type.substring(variable.type.length - 1); if (last === '?') { variable.occurrence = 0; variable.type = variable.type.substring(0, variable.type.length - 1); } else if (last === '*') { variable.occurrence = -1; variable.type = variable.type.substring(0, variable.type.length - 1); } else if (last === '+') { variable.occurrence = 2; variable.type = variable.type.substring(0, variable.type.length - 1); } else { variable.occurrence = 1; } } } catch (e) { //ignore resolution exceptions } this.visitOnly(node, ['ExprSingle', 'VarValue', 'VarDefaultValue']); return true; }; this.FunctionDecl = function (node) { //var updateAn = get(node, ['CompatibilityAnnotation'])[0]; //console.log(updateAn); var isUpdating = annotations['http://www.w3.org/2012/xquery#updating'] !== undefined; var typeDecl = get(node, ['SequenceType'])[0]; var name = get(node, ['EQName'])[0]; if (!typeDecl && !isUpdating) { addWarning('W05', 'Untyped return value', name.pos); } var isExternal = false; node.children.forEach(function (child) { if (child.name === 'TOKEN' && child.value === 'external') { isExternal = true; return false; } }); if (!isExternal) { pushSctx(node.pos); this.visitChildren(node); popSctx(); } return true; }; this.VarRef = function (node) { this.visitChildren(node, Handlers.VarRefHandler(translator, sctx, node)); return true; }; this.Param = function (node) { var typeDecl = get(node, ['TypeDeclaration'])[0]; if (!typeDecl) { addWarning('W05', 'Untyped function parameter', node.pos); } this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.InlineFunctionExpr = function (node) { pushSctx(node.pos); this.visitChildren(node); popSctx(); return true; }; //Scripting var statementCount = []; var handleStatements = function (node) { pushSctx(node.pos); statementCount.push(0); translator.visitChildren(node); for (var i = 1; i <= statementCount[statementCount.length - 1]; i++) { popSctx(node.pos); } statementCount.pop(); popSctx(); }; this.StatementsAndOptionalExpr = function (node) { handleStatements(node); return true; }; this.StatementsAndExpr = function (node) { handleStatements(node); return true; }; this.BlockStatement = function (node) { handleStatements(node); return true; }; this.VarDeclStatement = function (node) { pushSctx(node.pos); statementCount[statementCount.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); //return true; }; //FLWOR Expressions var clauses = []; this.FLWORExpr = this.FLWORStatement = function (node) { pushSctx(node.pos); clauses.push(0); this.visitChildren(node); for (var i = 1; i <= clauses[clauses.length - 1]; i++) { popSctx(node.pos); } clauses.pop(); popSctx(); return true; }; //this.ReturnClause = function(node){ //var expr = get(['ExprSingle', 'FLWORExpr']); // console.log(astAsXML(node)); //}; this.ForBinding = function (node) { this.visitOnly(node, ['ExprSingle', 'VarValue', 'VarDefaultValue']); pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.LetBinding = function (node) { this.visitOnly(node, ['ExprSingle', 'VarValue', 'VarDefaultValue']); pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.GroupingSpec = function (node) { var isVarDecl = false; node.children.forEach(function (child) { if (child.value === ':=') { isVarDecl = true; return false; } }); if (isVarDecl) { var groupingVariable = node.children[0]; this.visitOnly(node, ['ExprSingle', 'VarValue', 'VarDefaultValue']); pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(groupingVariable, Handlers.VarHandler(translator, sctx, groupingVariable)); return true; } else { } }; this.TumblingWindowClause = function (node) { this.visitOnly(node, ['ExprSingle']); pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); this.visitOnly(node, ['WindowStartCondition', 'WindowEndCondition']); return true; }; this.WindowVars = function (node) { pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.SlidingWindowClause = function (node) { this.visitOnly(node, ['ExprSingle', 'VarValue', 'VarDefaultValue']); pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); this.visitOnly(node, ['WindowStartCondition', 'WindowEndCondition']); return true; }; this.PositionalVar = function (node) { this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.PositionalVar = function (node) { this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.CurrentItem = function (node) { this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.PreviousItem = function (node) { this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.NextItem = function (node) { this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.CountClause = function (node) { pushSctx(node.pos); clauses[clauses.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; //SwitchExpr ::= 'switch' '(' Expr ')' SwitchCaseClause+ 'default' 'return' ExprSingle //SwitchCaseClause ::= ( 'case' SwitchCaseOperand )+ 'return' ExprSingle //SwitchCaseOperand ::= ExprSingle /* this.SwitchCaseClause = function(node){ pushSctx(node.pos); this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); this.visitOnly(node, ['ExprSingle']); popSctx(); return true; }; */ this.CaseClause = function (node) { pushSctx(node.pos); this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); this.visitOnly(node, ['ExprSingle']); popSctx(); return true; }; //CopyModifyExpr ::= 'copy' CopyModifySpec ( ',' CopyModifySpec )* 'modify' ExprSingle 'return' ExprSingle //CopyModifySpec ::= '$' VarName ':=' ExprSingle var copies = []; this.CopyModifyExpr = function (node) { pushSctx(node.pos); copies.push(0); this.visitChildren(node); for (var i = 1; i <= copies[copies.length - 1]; i++) { popSctx(node.pos); } copies.pop(); popSctx(); return true; }; this.CopyModifySpec = function (node) { this.visitOnly(node, ['ExprSingle']); pushSctx(node.pos); copies[copies.length - 1] += 1; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; // update apb this.updateExprs = function (node) { this.visitChildren(node, { SourceExpr: node => this.visitChildren(node), TargetExpr: node => this.visitChildren(node), ExprSingle: node => this.visitChildren(node) }); }; this.InsertExpr = this.updateExprs; this.DeleteExpr = this.updateExprs; this.ReplaceExpr = this.updateExprs; this.RenameExpr = this.updateExprs; //QuantifiedExpr ::= ( 'some' | 'every' ) QuantifiedVarDecl ( ',' QuantifiedVarDecl )* 'satisfies' ExprSingle //QuantifiedVarDecl ::= '$' VarName TypeDeclaration? 'in' ExprSingle var quantifiedDecls = []; this.QuantifiedExpr = function (node) { pushSctx(node.pos); quantifiedDecls.push(0); this.visitChildren(node); for (var i = 1; i <= quantifiedDecls[quantifiedDecls.length - 1]; i++) { popSctx(node.pos); } quantifiedDecls.pop(); popSctx(); return true; }; this.QuantifiedVarDecl = function (node) { this.visitOnly(node, ['ExprSingle']); pushSctx(node.pos); quantifiedDecls[quantifiedDecls.length - 1]++; this.visitChildren(node, Handlers.VarHandler(translator, sctx, node)); return true; }; this.NamedFunctionRef = function (node) { var name = translator.getFirstChild(node, 'EQName'); var arity = TreeOps.flatten(translator.getFirstChild(node, 'IntegerLiteral')); this.AddFunctionRef(name,arity,name.pos); /* var eqname = TreeOps.flatten(name); translator.apply(function () { var qname = sctx.resolveQName(eqname, node.pos); try { if (qname.uri !== '') { sctx.root.namespaces[qname.uri].used = true; } } catch (e) { //We ignore exceptions here to avoid duplicate markers } rootStcx.addFunctionRef(qname, arity, name.pos); }); */ return true; }; this.FunctionCall = function (node) { this.visitOnly(node, ['ArgumentList']); var name = translator.getFirstChild(node, 'FunctionEQName'); var arity = get(node, ['ArgumentList', 'Argument']).length; this.AddFunctionRef(name,arity,node.pos); /* var eqname = TreeOps.flatten(name); //console.log("FunctionCall",eqname) translator.apply(function () { var qname = sctx.resolveQName(eqname, node.pos); try { if (qname.uri !== '') { sctx.root.namespaces[qname.uri].used = true; } } catch (e) { //We ignore exceptions here to avoid duplicate markers } rootStcx.addFunctionRef(qname, arity, name.pos); }); */ return true; }; this.AddFunctionRef= function(name, arity,pos){ var eqname = TreeOps.flatten(name); translator.apply(function () { var qname = sctx.resolveQName(eqname, name.pos); try { if (qname.uri !== '') { sctx.root.namespaces[qname.uri].used = true; } } catch (e) { //We ignore exceptions here to avoid duplicate markers } rootStcx.addFunctionRef(qname, arity, pos); }); }; this.TryClause = function (node) { pushSctx(node.pos); this.visitChildren(node); popSctx(); return true; }; this.CatchClause = function (node) { pushSctx(node.pos); var prefix = 'err'; var uri = 'http://www.w3.org/2005/xqt-errors'; var emptyPos = { sl: 0, sc: 0, el: 0, ec: 0 }; sctx.addVariable({ prefix: prefix, uri: uri, name: 'code' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'description' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'value' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'module' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'line-number' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'column-number' }, 'CatchVar', emptyPos); sctx.addVariable({ prefix: prefix, uri: uri, name: 'additional' }, 'CatchVar', emptyPos); this.visitChildren(node); popSctx(); return true; }; this.Pragma = function (node) { var qname = TreeOps.flatten(get(node, ['EQName'])[0]); qname = rootStcx.resolveQName(qname, node); var value = TreeOps.flatten(get(node, ['PragmaContents'])[0]); if (qname.name === 'xqlint' && qname.uri === 'http://xqlint.io') { pushSctx(node.pos); var commands = value.match(/[a-zA-Z]+\(([^)]+)\)/g); commands.forEach(function (command) { var name = command.substring(0, command.indexOf('(')); var args = command.substring(0, command.length - 1).substring(command.indexOf('(') + 1).split(',').map(function (val) { return val.trim(); }); if (name === 'varrefs') { args.forEach(function (arg) { var qname = sctx.resolveQName(arg.substring(1), node.pos); if (qname.uri !== '') { sctx.root.namespaces[qname.uri].used = true; } sctx.addVarRef(qname, node.pos); }); } }); this.visitChildren(node); popSctx(); return true; } }; this.ArrowExpr= function(node){ this.visitChildren(node); var name; for (let index = 1; index < node.children.length; index++) { const cnode = node.children[index]; switch (cnode.name) { case 'ArrowFunctionSpecifier': name = translator.getFirstChild(cnode, 'EQName'); break; case 'ArgumentList': var arity = 1+get(cnode, ['Argument']).length; if(name){ this.AddFunctionRef(name,arity,name.pos); name=undefined; } break; default: break; } } return true; }; this.visit = function (node) { var name = node.name; //console.log("visit:",name) var skip = false; if (typeof this[name] === 'function') { skip = this[name](node) === true; } if (!skip) { this.visitChildren(node); } }; this.visitChildren = function (node, handler) { for (var i = 0; i < node.children.length; i++) { var child = node.children[i]; if (handler !== undefined && typeof handler[child.name] === 'function') { handler[child.name](child); } else { this.visit(child); } } }; this.visit(ast); // Object.keys(rootStcx.functionRefs).filter(function (ref) { return ref[0] === '#'; }).forEach(function (ref) { rootStcx.functionRefs[rootStcx.defaultFunctionNamespace + ref] = rootStcx.functionRefs[ref]; delete rootStcx.functionRefs[ref]; }); Object.keys(rootStcx.functions) .filter(function (fn) { return fn.substring(0, 'http://www.w3.org/2005/xquery-local-functions'.length) === 'http://www.w3.org/2005/xquery-local-functions'; }) .map(function (fn) { var name = fn.substring('http://www.w3.org/2005/xquery-local-functions'.length + 1); if (!rootStcx.functionRefs[fn] && name[0] !== '_') { addWarning('W03', 'Unused function ' + fn.substring('http://www.w3.org/2005/xquery-local-functions'.length + 1), rootStcx.functions[fn].pos); } }); // Object.keys(rootStcx.variables).forEach(function (key) { if (!rootStcx.varRefs[key] && (rootStcx.variables[key].annotations['http://www.w3.org/2005/xpath-functions#private'] || rootStcx.moduleNamespace === '') && rootStcx.variables[key].pos) { addWarning('W03', 'Unused variable "' + rootStcx.variables[key].name + '"', rootStcx.variables[key].pos); } }); // Object.keys(rootStcx.namespaces).forEach(function (uri) { var namespace = rootStcx.namespaces[uri]; if (namespace.used === undefined && !namespace.override && namespace.type === 'module') { addWarning('W04', 'Unused module "' + uri + '"', namespace.pos); } if (namespace.type === 'schema') { addWarning('XQST0009', 'No XML Schema support "' + uri + '"', namespace.pos); } }); };