xqlint
Version:
XQuery & JSONiq Quality Checker
463 lines (430 loc) • 16.9 kB
JavaScript
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;
};