diff --git a/packages/custom-functions-metadata/src/parseTree.ts b/packages/custom-functions-metadata/src/parseTree.ts index 269d1c646..9e39a97ef 100644 --- a/packages/custom-functions-metadata/src/parseTree.ts +++ b/packages/custom-functions-metadata/src/parseTree.ts @@ -133,7 +133,9 @@ const EXCLUDEFROMAUTOCOMPLETE = "excludefromautocomplete"; const HELPURL_PARAM = "helpurl"; const LINKEDENTITYLOADSERVICE = "linkedentityloadservice"; const REQUIRESADDRESS = "requiresaddress"; +const REQUIRESSTREAMADDRESS = "requiresstreamaddress"; const REQUIRESPARAMETERADDRESSES = "requiresparameteraddresses"; +const REQUIRESSTREAMPARAMETERADDRESSES = "requiresstreamparameteraddresses"; const STREAMING = "streaming"; const VOLATILE = "volatile"; const SUPPORT_SYNC = "supportsync"; @@ -688,16 +690,22 @@ function getOptions( isInvocationFunction: boolean, extra: IFunctionExtras ): IFunctionOptions { + const addressRequired = isAddressRequired(func); + const streamAddressRequired = isStreamAddressRequired(func); + const parameterAddressesRequired = isRequiresParameterAddresses(func); + const streamParameterAddressesRequired = isRequiresStreamParameterAddresses(func); + const hasStreamingTag = hasTag(func, STREAMING); + const streamEnabled = isStreaming(func, isStreamingFunction); + const optionsItem: IFunctionOptions = { cancelable: isCancelableTag(func, isCancelableFunction), - requiresAddress: isAddressRequired(func) && !isStreaming(func, isStreamingFunction), - requiresStreamAddress: isAddressRequired(func) && isStreaming(func, isStreamingFunction), - stream: isStreaming(func, isStreamingFunction), + requiresAddress: addressRequired && !streamEnabled, + requiresStreamAddress: streamAddressRequired || (addressRequired && streamEnabled), + stream: streamEnabled, volatile: isVolatile(func), - requiresParameterAddresses: - isRequiresParameterAddresses(func) && !isStreaming(func, isStreamingFunction), + requiresParameterAddresses: parameterAddressesRequired && !streamEnabled, requiresStreamParameterAddresses: - isRequiresParameterAddresses(func) && isStreaming(func, isStreamingFunction), + streamParameterAddressesRequired || (parameterAddressesRequired && streamEnabled), excludeFromAutoComplete: isExcludedFromAutoComplete(func), linkedEntityLoadService: isLinkedEntityLoadService(func), capturesCallingObject: capturesCallingObject(func), @@ -705,10 +713,23 @@ function getOptions( action: isAction(func), }; - if (isAddressRequired(func) || isRequiresParameterAddresses(func)) { - let errorParam: string = isAddressRequired(func) - ? "@requiresAddress" - : "@requiresParameterAddresses"; + if ( + addressRequired || + streamAddressRequired || + parameterAddressesRequired || + streamParameterAddressesRequired + ) { + let errorParam: string = ""; + + if (streamAddressRequired) { + errorParam = "@requiresStreamAddress"; + } else if (addressRequired) { + errorParam = "@requiresAddress"; + } else if (streamParameterAddressesRequired) { + errorParam = "@requiresStreamParameterAddresses"; + } else { + errorParam = "@requiresParameterAddresses"; + } if (!isStreamingFunction && !isCancelableFunction && !isInvocationFunction) { const functionPosition = getPosition(func, func.parameters.end); @@ -717,13 +738,28 @@ function getOptions( } } + if (streamAddressRequired && !hasStreamingTag) { + const functionPosition = getPosition(func, func.parameters.end); + const errorString = "@requiresStreamAddress can only be used with @streaming."; + extra.errors.push(logError(errorString, functionPosition)); + } + + if (streamParameterAddressesRequired && !hasStreamingTag) { + const functionPosition = getPosition(func, func.parameters.end); + const errorString = + "@requiresStreamParameterAddresses can only be used with @streaming."; + extra.errors.push(logError(errorString, functionPosition)); + } + if ( optionsItem.linkedEntityLoadService && (optionsItem.excludeFromAutoComplete || optionsItem.volatile || optionsItem.stream || optionsItem.requiresAddress || + optionsItem.requiresStreamAddress || optionsItem.requiresParameterAddresses || + optionsItem.requiresStreamParameterAddresses || optionsItem.capturesCallingObject) ) { let errorParam: string = ""; @@ -737,8 +773,12 @@ function getOptions( errorParam = "@streaming"; } else if (optionsItem.requiresAddress) { errorParam = "@requiresAddress"; + } else if (optionsItem.requiresStreamAddress) { + errorParam = "@requiresStreamAddress"; } else if (optionsItem.requiresParameterAddresses) { errorParam = "@requiresParameterAddresses"; + } else if (optionsItem.requiresStreamParameterAddresses) { + errorParam = "@requiresStreamParameterAddresses"; } else if (optionsItem.capturesCallingObject) { errorParam = "@capturesCallingObject"; } @@ -760,7 +800,9 @@ function getOptions( optionsItem.volatile || optionsItem.stream || optionsItem.requiresAddress || + optionsItem.requiresStreamAddress || optionsItem.requiresParameterAddresses || + optionsItem.requiresStreamParameterAddresses || optionsItem.capturesCallingObject || optionsItem.linkedEntityLoadService || optionsItem.supportSync) @@ -776,8 +818,12 @@ function getOptions( errorParam = "@streaming"; } else if (optionsItem.requiresAddress) { errorParam = "@requiresAddress"; + } else if (optionsItem.requiresStreamAddress) { + errorParam = "@requiresStreamAddress"; } else if (optionsItem.requiresParameterAddresses) { errorParam = "@requiresParameterAddresses"; + } else if (optionsItem.requiresStreamParameterAddresses) { + errorParam = "@requiresStreamParameterAddresses"; } else if (optionsItem.capturesCallingObject) { errorParam = "@capturesCallingObject"; } else if (optionsItem.linkedEntityLoadService) { @@ -1109,6 +1155,14 @@ function isAddressRequired(node: ts.Node): boolean { return hasTag(node, REQUIRESADDRESS); } +/** + * Returns true if requiresStreamAddress tag found in comments + * @param node jsDocs node + */ +function isStreamAddressRequired(node: ts.Node): boolean { + return hasTag(node, REQUIRESSTREAMADDRESS); +} + /** * Returns true if RequiresParameterAddresses tag found in comments * @param node jsDocs node @@ -1117,6 +1171,14 @@ function isRequiresParameterAddresses(node: ts.Node): boolean { return hasTag(node, REQUIRESPARAMETERADDRESSES); } +/** + * Returns true if requiresStreamParameterAddresses tag found in comments + * @param node jsDocs node + */ +function isRequiresStreamParameterAddresses(node: ts.Node): boolean { + return hasTag(node, REQUIRESSTREAMPARAMETERADDRESSES); +} + /** * Returns true if excludedFromAutoComplete tag found in comments * @param node jsDocs node diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.js.errors.txt b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.js.errors.txt new file mode 100644 index 000000000..d7f368857 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.js.errors.txt @@ -0,0 +1 @@ +@requiresStreamAddress can only be used with @streaming. (10,53) \ No newline at end of file diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.ts.errors.txt b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.ts.errors.txt new file mode 100644 index 000000000..bd0da17a7 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/expected.ts.errors.txt @@ -0,0 +1 @@ +@requiresStreamAddress can only be used with @streaming. (10,98) \ No newline at end of file diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.js b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.js new file mode 100644 index 000000000..18d4534d4 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.js @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Test requires stream address without the @streaming tag. + * @param {CustomFunctions.StreamingInvocation} invocation stream invocation + * @customfunction + * @requiresStreamAddress + */ +function streamAddressMissingStreamingTag(invocation) { + // Empty +} diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.ts b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.ts new file mode 100644 index 000000000..f70dd73c1 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddress-missing-streaming-tag/functions.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Test requires stream address without the @streaming tag. + * @param invocation stream invocation + * @customfunction + * @requiresStreamAddress + */ +function streamAddressMissingStreamingTag(invocation: CustomFunctions.StreamingInvocation) { + // Empty +} diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.js.errors.txt b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.js.errors.txt new file mode 100644 index 000000000..ac20f5668 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.js.errors.txt @@ -0,0 +1,2 @@ +@requiresStreamAddress can only be used with @streaming. (12,58) +@requiresStreamParameterAddresses can only be used with @streaming. (12,58) \ No newline at end of file diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.ts.errors.txt b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.ts.errors.txt new file mode 100644 index 000000000..27591d035 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/expected.ts.errors.txt @@ -0,0 +1,2 @@ +@requiresStreamAddress can only be used with @streaming. (12,94) +@requiresStreamParameterAddresses can only be used with @streaming. (12,94) \ No newline at end of file diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.js b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.js new file mode 100644 index 000000000..9c79bf227 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.js @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Test requires stream addresses without streaming. + * @param {string} x string + * @param {CustomFunctions.Invocation} invocation invocation + * @customfunction + * @requiresStreamAddress + * @requiresStreamParameterAddresses + */ +function streamAddressRequiresStreamingTest(x, invocation) { + // Empty +} diff --git a/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.ts b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.ts new file mode 100644 index 000000000..303fa960b --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/error-requiresstreamaddresses-missing-stream/functions.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Test requires stream addresses without streaming. + * @param x string + * @param invocation invocation + * @customfunction + * @requiresStreamAddress + * @requiresStreamParameterAddresses + */ +function streamAddressRequiresStreamingTest(x: string, invocation: CustomFunctions.Invocation) { + // Empty +} diff --git a/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/expected.json b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/expected.json new file mode 100644 index 000000000..e17c5dc13 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/expected.json @@ -0,0 +1,19 @@ +{ + "allowCustomDataForDataTypeAny": true, + "functions": [ + { + "description": "Streams the address of the current cell.", + "id": "ADDRESSSTREAM", + "name": "ADDRESSSTREAM", + "options": { + "requiresStreamAddress": true, + "stream": true, + "requiresStreamParameterAddresses": true + }, + "parameters": [], + "result": { + "type": "string" + } + } + ] +} \ No newline at end of file diff --git a/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.js b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.js new file mode 100644 index 000000000..d2aad4dc3 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.js @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Streams the address of the current cell. + * @customfunction ADDRESSSTREAM + * @param {CustomFunctions.StreamingInvocation} invocation stream invocation + * @streaming + * @requiresStreamAddress + * @requiresStreamParameterAddresses + */ +function addressStream(invocation) { + // Empty +} diff --git a/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.ts b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.ts new file mode 100644 index 000000000..aa60dd9c9 --- /dev/null +++ b/packages/custom-functions-metadata/test/cases/requiresstream-tags-explicit/functions.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Streams the address of the current cell. + * @customfunction ADDRESSSTREAM + * @param invocation stream invocation + * @streaming + * @requiresStreamAddress + * @requiresStreamParameterAddresses + */ +function addressStream(invocation: CustomFunctions.StreamingInvocation) { + // Empty +}