Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,10 @@ export class JSDocParser {
const attribute = attributes[scriptName].attributes[name];
const isAttribute = !!attribute;

// null for names that aren't identifiers (#private, ['computed'], 'quoted', 42)
const type = getType(member, typeChecker);

if (!SUPPORTED_ATTRIBUTE_TYPES.has(type.name)) {
if (!type || !SUPPORTED_ATTRIBUTE_TYPES.has(type.name)) {
continue;
}

Expand Down
7 changes: 6 additions & 1 deletion src/parsers/attribute-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,12 @@ export class AttributeParser {
getNodeAsAttribute(node: ts.Node, errors: ParsingError[] = [], scriptName?: string) {
const nameNode = (node as InternalNode).name;
const name = (nameNode && ts.isIdentifier(nameNode) && nameNode.text) as string;
const { type, name: typeName, array, isMixedUnion } = getType(node, this.typeChecker);

// null for names that aren't identifiers (#private, ['computed'], 'quoted', 42), which can't be attributes
const nodeType = getType(node, this.typeChecker);
if (!nodeType) return;

const { type, name: typeName, array, isMixedUnion } = nodeType;

const enums = this.getEnumMembers(node, errors);
let value: LiteralValue = null;
Expand Down
47 changes: 47 additions & 0 deletions test/fixtures/member-names.valid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable */
import { Script } from 'playcanvas';

/**
* @interface
*/
class Settings {
/**
* @attribute
* @type {number}
*/
level = 1;

#cache = null;
}

// Members whose names aren't identifiers (private, computed, quoted, numeric) can't be
// attributes. They must be skipped, not crash parsing of the whole script.
export class Example extends Script {
static scriptName = 'example';

/**
* @attribute
* @type {number}
*/
speed = 1;

/**
* @attribute
* @type {Settings}
*/
settings;

#secret = 2;

/**
* @attribute
* @type {number}
*/
#taggedSecret = 3;

['computed'] = 4;

'quoted' = 5;

42 = 6;
}
46 changes: 46 additions & 0 deletions test/tests/valid/member-names.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect } from 'chai';
import { describe, it, before } from 'mocha';

import { getAttributes, parseAttributes } from '../../utils.ts';

describe('VALID: Members whose names are not identifiers', function () {
describe('parseAttributes', function () {
let data;
before(async function () {
data = await parseAttributes('./member-names.valid.js');
});

it('parses without errors', function () {
expect(data[1]).to.be.empty;
expect(data[0]?.example).to.exist;
expect(data[0].example.errors).to.be.empty;
});

it('keeps the attributes with identifier names', function () {
expect(Object.keys(data[0].example.attributes)).to.have.members(['speed', 'settings']);
expect(data[0].example.attributes.speed.type).to.equal('number');
});

it('skips a private member of an interface type', function () {
const schema = data[0].example.attributes.settings.schema;
expect(schema.map(({ name }) => name)).to.deep.equal(['level']);
});
});

describe('getAttributes', function () {
let data;
before(async function () {
data = await getAttributes('./member-names.valid.js');
});

it('parses without errors', function () {
expect(data[1]).to.be.empty;
expect(data[0]?.example).to.exist;
});

it('lists only the members with identifier names', function () {
const names = data[0].example.map(({ name }) => name);
expect(names).to.not.include.members(['#secret', '#taggedSecret', "['computed']", "'quoted'", '42']);
});
});
});
Loading