From 4022673acac527003c82c9d5e2218b159ecc4d1e Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Fri, 9 Oct 2026 10:05:35 -0300 Subject: [PATCH 1/2] fix: mangle JS reserved words in generated function stubs (#23) Params and plain functions named var/default/delete etc. get a `_` prefix in the stub only, so jsdoc can parse the file. @param tags keep the real name, and functions get a @name tag. Co-Authored-By: Claude Haiku 5.5 --- CHANGELOG.md | 4 + src/convert-brighterscript-docs.spec.ts | 105 ++++++++++++++++++++++++ src/convert-brighterscript-docs.ts | 28 ++++++- 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a7b30a..f9fd774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Parameters and functions named with JavaScript reserved words (eg. `var`, `default`, `delete`) no longer break jsdoc's parser. The name is prefixed with `_` in the generated function stub only; the documented `@param` keeps the original name, and functions get a `@name` tag with their original name ([#23](https://github.com/markwpearce/brighterscript-jsdocs-plugin/issues/23)) + ## [1.0.0-alpha.56] - 2026-10-02 ### Fixed diff --git a/src/convert-brighterscript-docs.spec.ts b/src/convert-brighterscript-docs.spec.ts index 025a6c2..db23e1a 100644 --- a/src/convert-brighterscript-docs.spec.ts +++ b/src/convert-brighterscript-docs.spec.ts @@ -72,6 +72,111 @@ describe('convertBrighterscriptDocs', () => { `); }); + it('mangles params named with JS reserved words in the function stub only', () => { + expectOutput(cbd.convertBrighterscriptDocs(` + ' @param {dynamic} var - value to check + function isValid(var as dynamic, default = {} as object, delete = 1 as integer) as boolean + return var <> invalid + end function + `), ` + /** + * @function + * @param {dynamic} var value to check + * @param {object} [default={}] + * @param {integer} [delete=1] + * @returns {boolean} + */ + function isValid (_var, _default, _delete) { }; + `); + }); + + it('mangles function names that are JS reserved words in the stub, keeping the real name with @name', () => { + expectOutput(cbd.convertBrighterscriptDocs(` + ' removes a thing + function delete(index as integer) as boolean + return true + end function + namespace Utils + function var() as string + return "" + end function + end namespace + `), ` + /** + * removes a thing + * @function + * @param {integer} index + * @returns {boolean} + * @name delete + */ + function _delete (index) { }; + + /** + * @global + * @namespace Utils + */ + var Utils = {}; + + /** + * @function + * @memberof! Utils + * @returns {string} + * @name var + */ + function _var () { }; + + Utils._var = _var; + `); + }); + + it('mangles params named with JS reserved words in class method and interface stubs', () => { + expectOutput(cbd.convertBrighterscriptDocs(` + class Thing + sub new(var as dynamic) + end sub + function check(default as dynamic) as boolean + return true + end function + end class + interface Checker + function check(delete as dynamic) as boolean + end interface + `), ` + /** + */ + class Thing { + + /** + * @function + * @param {dynamic} var + * @constructor + * @returns {Thing} + */ + constructor(_var) { }; + + /** + * @function + * @param {dynamic} default + * @returns {boolean} + */ + check (_default) { }; + + } + + /** + * @interface + */ + function Checker() { }; + + /** + * @function + * @param {dynamic} delete + * @returns {boolean} + */ + Checker.prototype.check = function(_delete) { }; + `); + }); + it('keeps multi-line param and return descriptions with their tags', () => { expectOutput(cbd.convertBrighterscriptDocs(` ' Say hello diff --git a/src/convert-brighterscript-docs.ts b/src/convert-brighterscript-docs.ts index 063b0ba..c91a4e1 100644 --- a/src/convert-brighterscript-docs.ts +++ b/src/convert-brighterscript-docs.ts @@ -20,6 +20,23 @@ const escapeCharEntities = { const typeGetOptions: bs.GetTypeOptions = { flags: bs.SymbolTypeFlag.typetime }; +// Words that can't be used as a JS parameter name, but are allowed as a BrightScript one +const jsReservedWords = new Set([ + 'arguments', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', + 'delete', 'do', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'finally', 'for', 'function', + 'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'null', 'package', + 'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', + 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield' +]); + +/** + * Prefixes a name with `_` if it is a JS reserved word, so it can be used in the generated + * function stub without breaking jsdoc's parser. The `@param` tags keep the original name. + */ +function toJsSafeIdentifier(name: string): string { + return jsReservedWords.has(name.toLowerCase()) ? `_${name}` : name; +} + /** * Converts BrightScript hex literal tokens (`&hFF`) to valid JS literal syntax (`0xFF`) so * jsdoc's parser doesn't choke on the raw BrightScript source text. @@ -325,7 +342,7 @@ function processFunction(func: bs.FunctionStatement | bs.InterfaceMethodStatemen // Find the param line in the comments that match each param for (const param of params) { let paramName = param.tokens.name.text; - paramNameList.push(paramName); + paramNameList.push(toJsSafeIdentifier(paramName)); let paramType = getTypeName(param.getType(typeGetOptions)); let paramDescription = ''; @@ -414,7 +431,9 @@ function processFunction(func: bs.FunctionStatement | bs.InterfaceMethodStatemen } const funcName = bs.isInterfaceMethodStatement(func) ? func.tokens.name.text : func.tokens.name.text; - let funcDeclaration = `function ${funcName} (${paramNameList.join(', ')}) { }; \n`; + // a reserved word is only a problem as a plain function's name - class/interface members can use them + const stubFuncName = bs.isFunctionStatement(func) ? toJsSafeIdentifier(funcName) : funcName; + let funcDeclaration = `function ${stubFuncName} (${paramNameList.join(', ')}) { }; \n`; if (bs.isInterfaceMethodStatement(func)) { const iFaceName = (func.parent as bs.InterfaceStatement)?.name; totalOutput.push(returnLine); @@ -433,13 +452,16 @@ function processFunction(func: bs.FunctionStatement | bs.InterfaceMethodStatemen } } else { totalOutput.push(returnLine); + if (stubFuncName !== funcName) { + totalOutput.push(` * @name ${funcName}`); + } } totalOutput.push(' */'); totalOutput.push(funcDeclaration); if (namespaceName && bs.isFunctionStatement(func)) { - totalOutput.push(`${namespaceName}.${funcName} = ${funcName}; `); + totalOutput.push(`${namespaceName}.${stubFuncName} = ${stubFuncName}; `); } return totalOutput.join('\n'); From 86a8951ff9348de5a61e1c923684fad2451c3c32 Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Fri, 9 Oct 2026 10:45:23 -0300 Subject: [PATCH 2/2] chore: remove stale parentNamespaceName @param from processNamespace docs Co-Authored-By: Claude Opus 5.5 --- src/convert-brighterscript-docs.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/convert-brighterscript-docs.ts b/src/convert-brighterscript-docs.ts index c91a4e1..b43ed18 100644 --- a/src/convert-brighterscript-docs.ts +++ b/src/convert-brighterscript-docs.ts @@ -569,7 +569,6 @@ function processClass(klass: bs.ClassStatement, moduleName = '', namespaceName = * * @param namespace the actual namespace statement * @param moduleName [moduleName=""] the module name this namespace is in - * @param parentNamespaceName [parentNamespaceName=""] the namespace this namespace is in * @returns the jsdoc string for the namespace provided */ function processNamespace(namespace: bs.NamespaceStatement, moduleName = ''): string {