diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f13e8f575..5cd687269 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,9 +12,9 @@ jobs: fail-fast: false matrix: node-version: + - 22 - 20 - 18 - - 16 steps: - uses: actions/checkout@v2 with: diff --git a/src/expressions/functions/argumentHelper.ts b/src/expressions/functions/argumentHelper.ts index 1d521fab8..a13bf1f0a 100644 --- a/src/expressions/functions/argumentHelper.ts +++ b/src/expressions/functions/argumentHelper.ts @@ -3,6 +3,7 @@ import castToType from '../dataTypes/castToType'; import ISequence from '../dataTypes/ISequence'; import isSubtypeOf from '../dataTypes/isSubtypeOf'; import promoteToType from '../dataTypes/promoteToType'; +import sequenceFactory from '../dataTypes/sequenceFactory'; import Value, { SequenceMultiplicity, SequenceType, @@ -10,7 +11,11 @@ import Value, { ValueType, valueTypeToString, } from '../dataTypes/Value'; +import DynamicContext from '../DynamicContext'; import ExecutionParameters from '../ExecutionParameters'; +import StaticContext from '../StaticContext'; +import { errXPDY0002 } from '../XPathErrors'; +import FunctionDefinitionType from './FunctionDefinitionType'; function mapItem( argumentItem: Value, @@ -149,3 +154,31 @@ export const performFunctionConversion = ( }, }); }; + +export function contextItemAsFirstArgument( + functionName: string, + parameterType: ValueType, + fn: FunctionDefinitionType, +): FunctionDefinitionType { + return ( + dynamicContext: DynamicContext, + executionParameters: ExecutionParameters, + staticContext: StaticContext, + ): ISequence => { + if (dynamicContext.contextItem === null) { + throw errXPDY0002( + `The function ${functionName} depends on dynamic context, which is absent.`, + ); + } + + const contextItemArgument = performFunctionConversion( + { type: parameterType, mult: SequenceMultiplicity.EXACTLY_ONE }, + sequenceFactory.singleton(dynamicContext.contextItem), + executionParameters, + functionName, + false, + ); + + return fn(dynamicContext, executionParameters, staticContext, contextItemArgument); + }; +} diff --git a/src/expressions/functions/builtInFunctions_node.ts b/src/expressions/functions/builtInFunctions_node.ts index 420068313..8f4be779e 100644 --- a/src/expressions/functions/builtInFunctions_node.ts +++ b/src/expressions/functions/builtInFunctions_node.ts @@ -19,40 +19,18 @@ import isSubtypeOf from '../dataTypes/isSubtypeOf'; import sequenceFactory from '../dataTypes/sequenceFactory'; import Value, { SequenceMultiplicity, ValueType } from '../dataTypes/Value'; import QName from '../dataTypes/valueTypes/QName'; -import DynamicContext from '../DynamicContext'; -import ExecutionParameters from '../ExecutionParameters'; import arePointersEqual from '../operators/compares/arePointersEqual'; import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; -import StaticContext from '../StaticContext'; import { IterationHint } from '../util/iterators'; import zipSingleton from '../util/zipSingleton'; import { errXPDY0002 } from '../XPathErrors'; +import { contextItemAsFirstArgument } from './argumentHelper'; import { BuiltinDeclarationType } from './builtInFunctions'; import builtinStringFunctions from './builtInFunctions_string'; import FunctionDefinitionType from './FunctionDefinitionType'; import generateId from './generateId'; const fnString = builtinStringFunctions.functions.string; -function contextItemAsFirstArgument( - functionName: string, - fn: FunctionDefinitionType, - dynamicContext: DynamicContext, - executionParameters: ExecutionParameters, - staticContext: StaticContext, -) { - if (dynamicContext.contextItem === null) { - throw errXPDY0002( - `The function ${functionName} depends on dynamic context, which is absent.`, - ); - } - return fn( - dynamicContext, - executionParameters, - staticContext, - sequenceFactory.singleton(dynamicContext.contextItem), - ); -} - const fnNodeName: FunctionDefinitionType = ( _dynamicContext, executionParameters, @@ -499,7 +477,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'name', fnName), + callFunction: contextItemAsFirstArgument('name', ValueType.NODE, fnName), localName: 'name', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE }, @@ -515,7 +493,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'namespace-uri', fnNamespaceURI), + callFunction: contextItemAsFirstArgument('namespace-uri', ValueType.NODE, fnNamespaceURI), localName: 'namespace-uri', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSANYURI, mult: SequenceMultiplicity.EXACTLY_ONE }, @@ -550,7 +528,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'has-children', fnHasChildren), + callFunction: contextItemAsFirstArgument('has-children', ValueType.NODE, fnHasChildren), localName: 'has-children', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE }, @@ -566,7 +544,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'path', fnPath), + callFunction: contextItemAsFirstArgument('path', ValueType.NODE, fnPath), localName: 'path', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }, @@ -582,7 +560,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'node-name', fnNodeName), + callFunction: contextItemAsFirstArgument('node-name', ValueType.NODE, fnNodeName), localName: 'node-name', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSQNAME, mult: SequenceMultiplicity.ZERO_OR_ONE }, @@ -598,7 +576,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'local-name', fnLocalName), + callFunction: contextItemAsFirstArgument('local-name', ValueType.NODE, fnLocalName), localName: 'local-name', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE }, @@ -614,7 +592,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'root', fnRoot), + callFunction: contextItemAsFirstArgument('root', ValueType.NODE, fnRoot), localName: 'root', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.NODE, mult: SequenceMultiplicity.ZERO_OR_ONE }, @@ -622,7 +600,7 @@ const declarations: BuiltinDeclarationType[] = [ { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'data', fnData), + callFunction: contextItemAsFirstArgument('data', ValueType.ITEM, fnData), localName: 'data', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_MORE }, @@ -654,7 +632,7 @@ const declarations: BuiltinDeclarationType[] = [ }, { argumentTypes: [], - callFunction: contextItemAsFirstArgument.bind(null, 'generate-id', fnGenerateId), + callFunction: contextItemAsFirstArgument('generate-id', ValueType.NODE, fnGenerateId), localName: 'generate-id', namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE }, diff --git a/src/expressions/functions/builtInFunctions_string.ts b/src/expressions/functions/builtInFunctions_string.ts index 291c775be..5e7d4f859 100644 --- a/src/expressions/functions/builtInFunctions_string.ts +++ b/src/expressions/functions/builtInFunctions_string.ts @@ -16,6 +16,7 @@ import { errXPDY0002 } from '../XPathErrors'; import { BuiltinDeclarationType } from './builtInFunctions'; import builtInNumericFunctions from './builtInFunctions_numeric'; import FunctionDefinitionType from './FunctionDefinitionType'; +import { contextItemAsFirstArgument } from './argumentHelper'; const fnRound = builtInNumericFunctions.functions.round; @@ -23,25 +24,6 @@ function collationError(): ISequence { throw new Error('FOCH0002: No collations are supported'); } -function contextItemAsFirstArgument( - fn: FunctionDefinitionType, - dynamicContext: DynamicContext, - executionParameters: ExecutionParameters, - staticContext: StaticContext, -) { - if (dynamicContext.contextItem === null) { - throw errXPDY0002( - 'The function which was called depends on dynamic context, which is absent.', - ); - } - return fn( - dynamicContext, - executionParameters, - staticContext, - sequenceFactory.singleton(dynamicContext.contextItem), - ); -} - const fnCompare: FunctionDefinitionType = ( _dynamicContext, _executionParameters, @@ -767,8 +749,9 @@ const declarations: BuiltinDeclarationType[] = [ localName: 'normalize-space', argumentTypes: [], returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE }, - callFunction: contextItemAsFirstArgument.bind( - null, + callFunction: contextItemAsFirstArgument( + 'normalize-space', + ValueType.XSSTRING, ( dynamicContext: DynamicContext, executionParameters: ExecutionParameters, @@ -820,7 +803,7 @@ const declarations: BuiltinDeclarationType[] = [ localName: 'string', argumentTypes: [], returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE }, - callFunction: contextItemAsFirstArgument.bind(null, fnString), + callFunction: contextItemAsFirstArgument('string', ValueType.ITEM, fnString), }, { @@ -926,8 +909,11 @@ const declarations: BuiltinDeclarationType[] = [ localName: 'string-length', argumentTypes: [], returnType: { type: ValueType.XSINTEGER, mult: SequenceMultiplicity.EXACTLY_ONE }, - callFunction: contextItemAsFirstArgument.bind( - null, + callFunction: contextItemAsFirstArgument( + 'string-length', + // Note: deviate from the spec to allow expressions line `1!fn:string-length()` to + // atomize the input still + ValueType.XSANYATOMICTYPE, ( dynamicContext: DynamicContext, executionParameters: ExecutionParameters, diff --git a/test/assets/unrunnableTestCases.csv b/test/assets/unrunnableTestCases.csv index 3dcc816c4..80dc07251 100644 --- a/test/assets/unrunnableTestCases.csv +++ b/test/assets/unrunnableTestCases.csv @@ -726,11 +726,8 @@ generate-id-015,Error: No selector counterpart for: computedDocumentConstructor. generate-id-016,Error: No selector counterpart for: computedDocumentConstructor. generate-id-017,Error: No selector counterpart for: computedDocumentConstructor. generate-id-018,Error: XPTY0004: Multiplicity of function argument of type node()? for generate-id is incorrect. Expected "?", but got "+". -fn-has-children-007,AssertionError: Expected executing the XPath "(1)[fn:has-children()]" to resolve to one of the expected results, but got AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…'. -fn-has-children-008,AssertionError: Expected executing the XPath "(fn:concat#2)[fn:has-children()]" to resolve to one of the expected results, but got AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…'. fn-has-children-012,AssertionError: expected [Function] to throw an error fn-has-children-013,AssertionError: expected [Function] to throw an error -fn-has-children-014,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…' fn-hours-from-dateTime-3,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}adjust-dateTime-to-timezone with arity of 2 not registered. Did you mean "Q{test}custom-dateTime-function ()"? fn-hours-from-time-4,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}adjust-time-to-timezone with arity of 2 not registered. No similar functions found. fn-id-4,Error: No selector counterpart for: computedDocumentConstructor. @@ -981,8 +978,6 @@ fn-node-name-15,Error: No selector counterpart for: computedDocumentConstructor. fn-node-name-16,Error: No selector counterpart for: computedDocumentConstructor. fn-node-name-17,Error: No selector counterpart for: computedDocumentConstructor. fn-node-name-28,Error: No selector counterpart for: computedNamespaceConstructor. -fn-node-name-30,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…' -fn-node-name-31,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…' fn-notintg1args-1,Error: FOCA0003: can not cast -999999999999999999 to xs:integer, it is out of bounds for JavaScript numbers. fn-notintg1args-2,Error: FOCA0003: can not cast 830993497117024304 to xs:integer, it is out of bounds for JavaScript numbers. fn-notintg1args-3,Error: FOCA0003: can not cast 999999999999999999 to xs:integer, it is out of bounds for JavaScript numbers. @@ -2461,8 +2456,8 @@ cbcl-cast-gYearMonth-003,AssertionError: expected [Function] to throw an error XQueryComment014,Error: 3: 4: 5: "10" cast as (: type comment :) xs:integer ? ^ 6: 7: = 10 Error: XPST0003: Failed to parse script. Expected end of input at <>:5:44 - 5:45 Constr-compattr-compname-20,Error: XQDY0074: The value "Q{http://example.com/x}y" of a name expressions cannot be converted to an expanded QName. Constr-compattr-compname-21,Error: XQDY0074: The value " Q{}y " of a name expressions cannot be converted to an expanded QName. -Constr-compattr-compname-22,Error: XQDY0074: The value " Q{http://example.com/x}y2024" of a name expressions cannot be converted to an expanded QName. -Constr-compattr-compname-23,Error: XQDY0074: The value "Q{}y2024" of a name expressions cannot be converted to an expanded QName. +Constr-compattr-compname-22,Error: XQDY0074: The value " Q{http://example.com/x}y2025" of a name expressions cannot be converted to an expanded QName. +Constr-compattr-compname-23,Error: XQDY0074: The value "Q{}y2025" of a name expressions cannot be converted to an expanded QName. Constr-compattr-id-2,AssertionError: Expected executing the XPath "element elem {attribute xml:id {" ab c d "}}" to resolve to one of the expected results, but got AssertionError: Expected XPath element elem {attribute xml:id {" ab c d "}} to resolve to the given XML. Expected to equal , AssertionError: expected [Function] to throw an error. K2-ComputeConAttr-34,AssertionError: Expected XPath { attribute name {xs:hexBinary("ff")}, attribute name2 {"content"} } to resolve to the given XML. Expected to equal K2-ComputeConAttr-48,AssertionError: Expected executing the XPath "string(attribute xml:id {" ab c d "})" to resolve to one of the expected results, but got AssertionError: string(attribute xml:id {" ab c d "}): expected ' ab c d ' to equal 'ab c d', AssertionError: expected [Function] to throw an error. @@ -2548,8 +2543,8 @@ cbcl-constr-compcomment-001,AssertionError: expected [Function] to throw an erro cbcl-constr-compcomment-002,AssertionError: expected [Function] to throw an error Constr-compelem-name-20,Error: XQDY0074: The value "Q{http://example.com/x}x" of a name expressions cannot be converted to an expanded QName. Constr-compelem-name-21,Error: XQDY0074: The value "Q{}x" of a name expressions cannot be converted to an expanded QName. -Constr-compelem-name-22,Error: XQDY0074: The value " Q{http://example.com/x}x2024" of a name expressions cannot be converted to an expanded QName. -Constr-compelem-name-23,Error: XQDY0074: The value " Q{}x2024 " of a name expressions cannot be converted to an expanded QName. +Constr-compelem-name-22,Error: XQDY0074: The value " Q{http://example.com/x}x2025" of a name expressions cannot be converted to an expanded QName. +Constr-compelem-name-23,Error: XQDY0074: The value " Q{}x2025 " of a name expressions cannot be converted to an expanded QName. Constr-compelem-name-24,AssertionError: element {" x" || year-from-date(current-date()) || " "} {}: expected false to be true Constr-compelem-constrmod-3,AssertionError: expected [Function] to throw error matching /FORG0001/ but got 'Not implemented: only module imports,…' Constr-compelem-constrmod-4,AssertionError: Expected executing the XPath "declare construction preserve; (element elem {xs:decimal((//decimal[1]))}) cast as xs:integer" to resolve to one of the expected results, but got Error: Not implemented: only module imports, namespace declarations, and function declarations are implemented in XQuery modules, AssertionError: expected [Function] to throw error matching /FORG0001/ but got 'Not implemented: only module imports,…'. diff --git a/test/specs/parsing/functions/functions.node.tests.ts b/test/specs/parsing/functions/functions.node.tests.ts index 520a84447..70562ebe3 100644 --- a/test/specs/parsing/functions/functions.node.tests.ts +++ b/test/specs/parsing/functions/functions.node.tests.ts @@ -502,6 +502,22 @@ return $node/root() = $element`, ); }); + it('returns the correct error if the context argument is of the wrong type', () => { + documentNode = slimdom.parseXmlDocument( + `

+ Freude, schöner Götterfunken,
+ Tochter aus Elysium,
+ Wir betreten feuertrunken,
+ Himmlische, dein Heiligtum.

`, + ); + + chai.assert.throws(() => { + evaluateXPathToString(`'string'!path()`, documentNode, null, null, { + language: evaluateXPath.XQUERY_3_1_LANGUAGE, + }); + }, 'XPTY0004'); + }); + it('returns "/Q{http://example.com/one}p[1]" for the

element of the document', () => { documentNode = slimdom.parseXmlDocument( `