Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions src/convert-brighterscript-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 25 additions & 4 deletions src/convert-brighterscript-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 = '';

Expand Down Expand Up @@ -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);
Expand All @@ -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');
Expand Down Expand Up @@ -547,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 {
Expand Down
Loading