UNPKG

xqlint

Version:

XQuery & JSONiq Quality Checker

463 lines (430 loc) 16.9 kB
exports.StaticContext = function (parent, pos, processor) { 'use strict'; var TreeOps = require('../tree_ops').TreeOps; var Errors = require('./errors'); var StaticError = Errors.StaticError; var StaticWarning = Errors.StaticWarning; var getSchemaBuiltinTypes = require('./schema_built-in_types').getSchemaBuiltinTypes; var emptyPos = { sl:0, sc: 0, el: 0, ec: 0 }; var namespaces = {}; var getVarKey = function(qname) { return qname.uri + '#' + qname.name; }; var getFnKey = function(qname, arity) { return getVarKey(qname) + '#' + arity; }; if(!parent) { namespaces['http://jsoniq.org/functions'] = { prefixes: ['jn'], pos: emptyPos, type: 'module', override: true }; namespaces['http://jsoniq.org/function-library'] = { prefixes: ['libjn'], pos: emptyPos, type: 'module', override: true }; //namespaces['http://www.28msec.com/modules/mongodb/types'] = { // prefix: 'ms', // pos: emptyPos, // type: 'module', // override: true //}; namespaces['http://www.w3.org/2005/xpath-functions'] = { prefixes: ['fn'], pos: emptyPos, type: 'module', override: true }; namespaces['http://www.w3.org/2005/xquery-local-functions'] = { prefixes: ['local'], pos: emptyPos, type: 'declare', override: true }; namespaces['http://www.w3.org/2001/XMLSchema-instance'] = { prefixes: ['xsi'], pos: emptyPos, type: 'declare' }; namespaces['http://www.w3.org/2001/XMLSchema'] = { prefixes: ['xs'], pos: emptyPos, type: 'declare' }; namespaces['http://www.w3.org/XML/1998/namespace'] = { prefixes: ['xml'], pos: emptyPos, type: 'declare' }; namespaces['http://www.w3.org/2005/xqt-errors'] = { prefixes: ['err'], pos: emptyPos, type: 'declare', override: true }; } var s = { parent: parent, children: [], pos: pos, setModuleResolver: function(resolver){ this.root.moduleResolver = resolver; return this; }, setModules: function(index){ if(this !== this.root){ throw new Error('setModules() not invoked from the root static context.'); } this.moduleResolver = function(uri){ return index[uri]; }; var that = this; Object.keys(this.namespaces).forEach(function(uri){ var ns = that.namespaces[uri]; if(ns.type === 'module') { var mod = that.moduleResolver(uri); if(mod.variables) { TreeOps.concat(that.variables, mod.variables); } if(mod.functions) { TreeOps.concat(that.functions, mod.functions); } } }); return this; }, setModulesFromXQDoc: function(xqdoc){ if(this !== this.root){ throw new Error('setModulesFromXQDoc() not invoked from the root static context.'); } //We throw away all the unecessary xqdoc information var index = {}; Object.keys(xqdoc).forEach(function(uri) { var mod = xqdoc[uri]; var variables = {}; var functions = {}; mod.functions.forEach(function(fn){ functions[uri + '#' + fn.name + '#' + fn.arity] = { params: [], annotations: [], name: fn.name, arity: fn.arity, eqname: { uri: uri, name: fn.name } }; fn.parameters.forEach(function(param){ functions[uri + '#' + fn.name + '#' + fn.arity].params.push('$' + param.name); }); }); mod.variables.forEach(function(variable){ var name = variable.name.substring(variable.name.indexOf(':') + 1); variables[uri + '#' + name] = { type: 'VarDecl', annotations: [], eqname: { uri: uri, name: name } }; }); index[uri] = { variables: variables, functions: functions }; }); this.root.moduleResolver = function(uri){ return index[uri]; }; var that = this; Object.keys(this.namespaces).forEach(function(uri){ var ns = that.namespaces[uri]; if(ns.type === 'module') { var mod = that.moduleResolver(uri); if(mod.variables) { TreeOps.concat(that.variables, mod.variables); } if(mod.functions) { TreeOps.concat(that.functions, mod.functions); } } }); return this; }, moduleNamespace: '', description: '', defaultFunctionNamespace: 'http://www.w3.org/2005/xpath-functions', defaultFunctionNamespaces: [ 'http://www.28msec.com/modules/collections', 'http://www.28msec.com/modules/store', 'http://jsoniq.org/functions', 'http://jsoniq.org/function-library', 'http://www.w3.org/2001/XMLSchema' //Built-in type constructors ], defaultElementNamespace: '', namespaces: namespaces, availableModuleNamespaces: [], importModule: function(uri, prefix, pos) { if(this !== this.root){ throw new Error('Function not invoked from the root static context.'); } this.addNamespace(uri, prefix, pos, 'module'); if(this.moduleResolver) { try { var mod = this.moduleResolver(uri, []); if(mod.variables) { TreeOps.concat(this.variables, mod.variables); } if(mod.functions) { TreeOps.concat(this.functions, mod.functions); } } catch(e) { throw new StaticError('XQST0059', 'module "' + uri + '" not found', pos); } } return this; }, getAvailableModuleNamespaces: function(){ return this.root.availableModuleNamespaces; }, getPrefixesByNamespace: function(uri){ return this.root.namespaces[uri].prefixes; }, addNamespace: function (uri, prefix, pos, type) { if(prefix === '' && type === 'module') { throw new StaticWarning('W01', 'Avoid this type of import. Use import module namespace instead', pos); } //Check for empty target namespace if (uri === '') { throw new StaticError('XQST0088', 'empty target namespace in module import or module declaration', pos); } //Check for duplicate target namespace var namespace = this.getNamespace(uri); if (namespace && namespace.type === type && type !== 'declare' && !namespace.override) { throw new StaticError('XQST0047', '"' + uri + '": duplicate target namespace', pos); } //Check for duplicate prefix namespace = this.getNamespaceByPrefix(prefix); if (namespace && !namespace.override) { throw new StaticError('XQST0033', '"' + prefix + '": namespace prefix already bound to "' + namespace.uri + '"', pos); } namespace = this.namespaces[uri]; var prefixes = [prefix]; if(namespace) { prefixes = prefixes.concat(this.namespaces[uri].prefixes); } this.namespaces[uri] = { prefixes: prefixes, pos: pos, type: type }; if (namespace) { throw new StaticWarning('W02', '"' + uri + '" already bound to the "' + namespace.prefixes.join(', ') + '" prefix', pos); } }, getNamespaces: function(){ return this.root.namespaces; }, getNamespace: function (uri) { var that = this; while (that) { var namespace = that.namespaces[uri]; if (namespace) { return namespace; } that = that.parent; } }, getNamespaceByPrefix: function (prefix) { var found = []; var that = this; var handler = function (uri) { var namespace = that.namespaces[uri]; if (namespace.prefixes.indexOf(prefix) !== -1) { namespace.uri = uri; found.push(namespace); } }; while (that) { Object.keys(that.namespaces).forEach(handler); that = that.parent; } var result; found.forEach(function(ns){ if(ns.type === 'moduleDecl') { result = ns; } }); if(result) { return result; } else { return found[0]; } }, resolveQName: function(value, pos){ var qname = { uri: '', prefix: '', name: '' }; var idx; if (value.substring(0, 2) === 'Q{') { idx = value.indexOf('}'); qname.uri = value.substring(2, idx); qname.name = value.substring(idx + 1); } else { idx = value.indexOf(':'); qname.prefix = value.substring(0, idx); var namespace = this.getNamespaceByPrefix(qname.prefix); if(!namespace && qname.prefix !== '' && ['fn', 'jn'].indexOf(qname.prefix) === -1) { throw new StaticError('XPST0081', '"' + qname.prefix + '": can not expand prefix of lexical QName to namespace URI', pos); } if(namespace) { qname.uri = namespace.uri; } qname.name = value.substring(idx + 1); } return qname; }, variables: {}, varRefs: {}, functionRefs: {}, addVariable: function(qname, type, pos){ if( type === 'VarDecl' && this.moduleNamespace !== '' && !(this.moduleNamespace === qname.uri || qname.uri === '') ) { throw new StaticError('XQST0048', '"' + qname.prefix + ':' + qname.name + '": Qname not library namespace', pos); } var key = getVarKey(qname); if(type === 'VarDecl' && this.variables[key]) { throw new StaticError('XQST0049', '"' + qname.name + '": duplicate variable declaration', pos); } this.variables[key] = { type: type, pos: pos, qname: qname, annotations: {} }; return this; }, getVariables: function(){ var variables = {}; var that = this; var handler = function(key){ if(!variables[key]){ variables[key] = that.variables[key]; } }; while(that){ Object.keys(that.variables).forEach(handler); that = that.parent; } return variables; }, getVariable: function(qname) { var key = getVarKey(qname); var that = this; while(that) { if(that.variables[key]) { return that.variables[key]; } that = that.parent; } }, addVarRef: function(qname, pos){ var varDecl = this.getVariable(qname); //If no moduleResolver is available, then only resolve 'local' variables if(!varDecl && (qname.uri === '' || this.root.moduleResolver)) { throw new StaticError('XPST0008', '"' + qname.name + '": undeclared variable', pos); } var key = getVarKey(qname); this.varRefs[key] = true; }, addFunctionRef: function(qname, arity, pos){ var fn = this.getFunction(qname, arity); if(!fn && (qname.uri === 'http://www.w3.org/2005/xquery-local-functions' || this.root.moduleResolver)){ //Is it concat? (variadic) if((qname.uri === 'http://www.w3.org/2005/xpath-functions' || (qname.uri === '' && this.root.defaultFunctionNamespaces.concat(this.root.defaultFunctionNamespace).indexOf('http://www.w3.org/2005/xpath-functions') !== -1)) && qname.name === 'concat') { //do nothing } else if(!fn){ throw new StaticError('XPST0008', '"' + qname.name + '#' + arity + '": undeclared function', pos); } } var key = getFnKey(qname, arity); this.functionRefs[key] = true; }, functions: getSchemaBuiltinTypes()['http://www.w3.org/2001/XMLSchema'].functions, //functions: {}, getFunctions: function(){ return this.root.functions; }, getFunction: function(qname, arity){ var key = getFnKey(qname, arity); var fn; if(qname.uri === '') { var that = this; this.root.defaultFunctionNamespaces.concat([this.root.defaultFunctionNamespace]).forEach(function(defaultFunctionNamespace){ if(!fn){ fn = that.getFunction({ uri: defaultFunctionNamespace, prefix: qname.prefix, name: qname.name }, arity); } else { return false; } }); return fn; } else { return this.root.functions[key]; } }, addFunction: function(qname, pos, params) { if(this !== this.root){ throw new Error('addFunction() not invoked from the root static context.'); } var arity = params.length; if( this.moduleNamespace !== '' && !(this.moduleNamespace === qname.uri || (qname.uri === '' && this.defaultFunctionNamespace === this.moduleNamespace)) ) { throw new StaticError('XQST0048', '"' + qname.prefix + ':' + qname.name + '": Qname not library namespace', pos); } var key = getFnKey(qname, arity); if(this.functions[key]) { throw new StaticError('XQST0034', '"' + qname.name + '": duplicate function declaration', pos); } this.functions[key] = { pos: pos, params: params }; return this; } }; s.root = parent ? parent.root : s; if(processor === '28msec') { namespaces['http://www.28msec.com/modules/collections'] = { prefixes: ['db'], pos: emptyPos, type: 'module', override: true }; namespaces['http://www.28msec.com/modules/store'] = { prefixes: ['store'], pos: emptyPos, type: 'module', override: true }; namespaces['http://zorba.io/annotations'] = { prefixes: ['an'], pos: emptyPos, type: 'declare', override: true }; namespaces['http://www.28msec.com/annotations/rest'] = { prefixes: ['rest'], pos: emptyPos, type: 'declare', override: true }; namespaces['http://zorba.io/errors'] = { prefixes: ['zerr'], pos: emptyPos, type: 'declare', override: true }; } return s; };