Skip to content

Commit

Permalink
Defined case sensitivity of doc parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
markwpearce committed Sep 6, 2024
1 parent 0fda0c1 commit 3d66f6a
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 22 deletions.
34 changes: 34 additions & 0 deletions src/bscPlugin/validation/BrsFileValidator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1172,5 +1172,39 @@ describe('BrsFileValidator', () => {
});

});

// Skipped until we can figure out how to handle @var tags
describe.skip('@var', () => {
it('uses @var type in brs file to define types of variables', () => {
const file = program.setFile<BrsFile>('source/main.brs', `
function getPie() as string
' @var {string} someDate
if m.top.isTrue
someDate = getDate()
else
someDate = m.date2
end if
if m.someProp
someDate = m.someProp.date
end if
return someDate
end function
function getDate()
return "Dec 25"
end function
`);
program.validate();
expectZeroDiagnostics(program);
const data = {} as ExtraSymbolData;
const funcStmt = file.ast.findChild(isFunctionStatement);
const returnStmt = funcStmt.findChild(isReturnStatement);
const varType = returnStmt.getSymbolTable().getSymbolType('someDate', { flags: SymbolTypeFlag.runtime, data: data });
expectTypeToBe(varType, StringType);
});

});
});
});
48 changes: 48 additions & 0 deletions src/bscPlugin/validation/BrsFileValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { DynamicType } from '../../types/DynamicType';
import util from '../../util';
import type { Range } from 'vscode-languageserver';
import type { Token } from '../../lexer/Token';
import type { BrightScriptDoc } from '../../parser/BrightScriptDocParser';
import brsDocParser from '../../parser/BrightScriptDocParser';

export class BrsFileValidator {
constructor(
Expand Down Expand Up @@ -238,6 +240,25 @@ export class BrsFileValidator {
// eslint-disable-next-line no-bitwise
node.parent.getSymbolTable().addSymbol(node.tokens.name.text, { definingNode: node, doNotMerge: true, isAlias: true }, targetType, SymbolTypeFlag.runtime | SymbolTypeFlag.typetime);

},
AstNode: (node) => {
//check for doc comments
if (!node.leadingTrivia || node.leadingTrivia.length === 0) {
return;
}
const doc = brsDocParser.parseNode(node);
if (doc.tags.length === 0) {
return;
}

let funcExpr = node.findAncestor<FunctionExpression>(isFunctionExpression);
if (funcExpr) {
// handle comment tags inside a function expression
this.processDocTagsInFunction(doc, node, funcExpr);
} else {
//handle comment tags outside of a function expression
this.processDocTagsAtTopLevel(doc, node);
}
}
});

Expand All @@ -248,6 +269,33 @@ export class BrsFileValidator {
});
}

private processDocTagsInFunction(doc: BrightScriptDoc, node: AstNode, funcExpr: FunctionExpression) {
//TODO: Handle doc tags that influence the function they're in

// For example, declaring variable types:
// const symbolTable = funcExpr.body.getSymbolTable();

// for (const varTag of doc.getAllTags(BrsDocTagKind.Var)) {
// const varName = (varTag as BrsDocParamTag).name;
// const varTypeStr = (varTag as BrsDocParamTag).type;
// const data: ExtraSymbolData = {};
// const type = doc.getTypeFromContext(varTypeStr, node, { flags: SymbolTypeFlag.typetime, fullName: varTypeStr, data: data, tableProvider: () => symbolTable });
// if (type) {
// symbolTable.addSymbol(varName, { ...data, isFromDocComment: true }, type, SymbolTypeFlag.runtime);
// }
// }
}

private processDocTagsAtTopLevel(doc: BrightScriptDoc, node: AstNode) {
//TODO:
// - handle import statements?
// - handle library statements?
// - handle typecast statements?
// - handle alias statements?
// - handle const statements?
// - allow interface definitions?
}

/**
* Validate that a statement is defined in one of these specific locations
* - the root of the AST
Expand Down
44 changes: 43 additions & 1 deletion src/parser/BrightScriptDocParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import { brsDocParser } from './BrightScriptDocParser';
import { Parser } from './Parser';

describe('BrightScriptbrsDocParser', () => {
describe('BrightScriptDocParser', () => {


it('should get a comment', () => {
Expand Down Expand Up @@ -155,6 +155,26 @@ describe('BrightScriptbrsDocParser', () => {
`);
expect(doc.getTypeTag().type).to.equal('integer');
});
it('finds the var tag', () => {
const doc = brsDocParser.parse(`
@var {integer} varName
`);
expect(doc.getVar('VarName').type).to.equal('integer');
});

it('is case INSENSITIVE for param names', () => {
const doc = brsDocParser.parse(`
@param {integer} ALLCAPS
`);
expect(doc.getParam('allcaps').type).to.equal('integer');
});

it('is case SENSITIVE for tag names', () => {
const doc = brsDocParser.parse(`
@ALLCAPS
`);
expect(doc.getTag('allcaps')).to.be.undefined;
});

describe('nodes', () => {
const parser = new Parser();
Expand Down Expand Up @@ -191,6 +211,28 @@ describe('BrightScriptbrsDocParser', () => {
expect(doc.getReturn().type).to.equal('integer');
});

it('should get documentation when it is wrapped in jsdoc /** */', () => {
let { ast } = parser.parse(`
' /**
' * My description
' * of this function
' * @param p1 this is p1
' * @param p2 this is p2
' * @return {integer} sum of p1 and p2
' */
function foo(p1, p2)
return p1 + p2
end function
`);

const doc = brsDocParser.parseNode(ast.statements[0]);
expect(doc.description).to.equal('My description\nof this function');
expect(doc.getAllTags('param').length).to.equal(2);
expect(doc.getParam('p1').description).to.equal('this is p1');
expect(doc.getParam('p2').description).to.equal('this is p2');
expect(doc.getReturn().description).to.equal('sum of p1 and p2');
expect(doc.getReturn().type).to.equal('integer');
});

});
});
66 changes: 45 additions & 21 deletions src/parser/BrightScriptDocParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const paramRegex = /(?:{([^}]*)}\s+)?(?:(\[?\w+\]?))\s*(.*)/;
const returnRegex = /(?:{([^}]*)})?\s*(.*)/;
const typeTagRegex = /(?:{([^}]*)})?/;

export enum BrsDocTagKind {
Description = 'description',
Param = 'param',
Return = 'return',
Type = 'type',
Var = 'var'
}


export class BrightScriptDocParser {

public parseNode(node: AstNode) {
Expand Down Expand Up @@ -72,17 +81,19 @@ export class BrightScriptDocParser {
if (!match) {
return;
}
const tagName = match[1].toLowerCase();
const tagName = match[1];
const detail = match[2] ?? '';

switch (tagName) {
case 'param':
case BrsDocTagKind.Param:
return this.parseParam(detail);
case 'return':
case BrsDocTagKind.Return:
case 'returns':
return this.parseReturn(detail);
case 'type':
case BrsDocTagKind.Type:
return this.parseType(detail);
case BrsDocTagKind.Var:
return { ...this.parseParam(detail), tagName: BrsDocTagKind.Var };
}
return {
tagName: tagName,
Expand All @@ -108,7 +119,7 @@ export class BrightScriptDocParser {
paramName = paramName.replace(/\[|\]/g, '').trim();
}
return {
tagName: 'param',
tagName: BrsDocTagKind.Param,
name: paramName,
type: type,
description: description,
Expand All @@ -126,7 +137,7 @@ export class BrightScriptDocParser {
description = match[2] ?? '';
}
return {
tagName: 'return',
tagName: BrsDocTagKind.Return,
type: type,
description: description,
detail: detail
Expand All @@ -142,14 +153,14 @@ export class BrightScriptDocParser {
}
}
return {
tagName: 'type',
tagName: BrsDocTagKind.Type,
type: type,
detail: detail
};
}
}

class BrightScriptDoc {
export class BrightScriptDoc {

protected _description: string;

Expand All @@ -166,7 +177,7 @@ class BrightScriptDoc {

get description() {
const descTag = this.tags.find((tag) => {
return tag.tagName === 'description';
return tag.tagName === BrsDocTagKind.Description;
});

let result = this._description ?? '';
Expand All @@ -178,34 +189,41 @@ class BrightScriptDoc {
}

getParam(name: string) {
const lowerName = name.toLowerCase();
return this.tags.find((tag) => {
return tag.tagName === BrsDocTagKind.Param && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
}) as BrsDocParamTag;
}

getVar(name: string) {
const lowerName = name.toLowerCase();
return this.tags.find((tag) => {
return tag.tagName === 'param' && (tag as BrsDocParamTag).name === name;
return tag.tagName === BrsDocTagKind.Var && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
}) as BrsDocParamTag;
}


getReturn() {
return this.tags.find((tag) => {
return tag.tagName === 'return' || tag.tagName === 'returns';
return tag.tagName === BrsDocTagKind.Return || tag.tagName === 'returns';
}) as BrsDocWithDescription;
}

getTypeTag() {
return this.tags.find((tag) => {
return tag.tagName === 'type';
return tag.tagName === BrsDocTagKind.Type;
}) as BrsDocWithType;
}

getTag(tagName: string) {
const lowerTagName = tagName.toLowerCase();
return this.tags.find((tag) => {
return tag.tagName === lowerTagName;
return tag.tagName === tagName;
});
}

getAllTags(tagName: string) {
const lowerTagName = tagName.toLowerCase();
return this.tags.filter((tag) => {
return tag.tagName === lowerTagName;
return tag.tagName === tagName;
});
}

Expand All @@ -215,6 +233,12 @@ class BrightScriptDoc {
return this.getTypeFromContext(param?.type, nodeContext, options);
}

getVarBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
const param = this.getVar(name);

return this.getTypeFromContext(param?.type, nodeContext, options);
}

getReturnBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
const retTag = this.getReturn();

Expand All @@ -227,7 +251,7 @@ class BrightScriptDoc {
return this.getTypeFromContext(retTag?.type, nodeContext, options);
}

private getTypeFromContext(typeName: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
getTypeFromContext(typeName: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
// TODO: Add support for union types here
const topSymbolTable = nodeContext?.getSymbolTable();
if (!topSymbolTable || !typeName) {
Expand All @@ -249,19 +273,19 @@ class BrightScriptDoc {
}
}

interface BrsDocTag {
export interface BrsDocTag {
tagName: string;
detail?: string;
}
interface BrsDocWithType extends BrsDocTag {
export interface BrsDocWithType extends BrsDocTag {
type?: string;
}

interface BrsDocWithDescription extends BrsDocWithType {
export interface BrsDocWithDescription extends BrsDocWithType {
description?: string;
}

interface BrsDocParamTag extends BrsDocWithDescription {
export interface BrsDocParamTag extends BrsDocWithDescription {
name: string;
optional?: boolean;
}
Expand Down

0 comments on commit 3d66f6a

Please sign in to comment.