playcanvas / playcanvas/attribute-parser

Parser throws on tuple `@type` tags and `= {}` initializers, and silently drops unresolvable types

Open
#73 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
1
Forks
2
PR merge metrics
No merged PRs in 30d

Description

Found while investigating a forum report about the Interface Attribute Arrays docs example (docs fix: playcanvas/developer-site#1200). Interface arrays themselves work correctly — these are three separate robustness problems I hit while narrowing it down.

All three reproduce on main (fa36853, v1.11.0) through parseAttributes(), with the playcanvas types loaded as in test/utils.ts. Each snippet below is a complete script file.

1. TypeError when @type is a tuple
import { Script } from 'playcanvas';

class GameLogic extends Script {
    static scriptName = 'gameLogic';

    /**
     * @attribute
     * @type {[number]}
     */
    values;
}

export { GameLogic };
TypeError: Cannot read properties of undefined (reading 'declarations')
    at ScriptParser.extractAttributes
    at JSDocParser.parseAttributes

src/parsers/script-parser.ts:497-498:

const symbol = type.aliasSymbol || type.symbol;
const typeNode = (symbol?.valueDeclaration || symbol.declarations?.[0]) as InternalNode;

A tuple type has no symbol, so symbol?.valueDeclaration is undefined and the second term dereferences undefined. symbol?.declarations?.[0] would fall through to the if (!typeNode) continue guard on the next line. Same for @type {[number, number]}.

2. TypeError when an attribute is initialized with an empty object literal
import { Script } from 'playcanvas';

class GameLogic extends Script {
    static scriptName = 'gameLogic';

    /**
     * @attribute
     */
    data = {};
}

export { GameLogic };
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
    at Array.from (<anonymous>)
    at ScriptParser.extractAttributes
    at JSDocParser.parseAttributes

src/parsers/script-parser.ts:553-560:

const members: readonly ts.Node[] =
    typeNode.members ??
    typeNode.properties ??
    Array.from(type.symbol.members as unknown as Iterable<[string, ts.Symbol]>).map(
        (entry) => entry[1].declarations[0]
    ) ??
    [];

For = {} both typeNode.members and typeNode.properties are nullish and type.symbol.members is undefined, so Array.from throws. Note the trailing ?? [] can never catch this, since the throw happens while evaluating the operand. data = { x: 1 } is fine — only the empty literal hits it.

This also fires for a member of an /** @interface */ class, e.g. data = {}; inside class Enemy.

Why 1 and 2 matter

Both throw out of extractAttributes, so the consumer gets no attributes and no diagnostics for the whole file instead of one error on the offending member. A half-written type annotation is a normal intermediate state while typing in the Editor's code editor, so these are easy to hit. Reporting a ParsingError for the member (as the neighbouring paths do) would keep the rest of the script parsing.

3. An unresolvable @type is dropped silently
import { Script } from 'playcanvas';

class GameLogic extends Script {
    static scriptName = 'gameLogic';

    /**
     * @attribute
     * @type {Enemy[]}
     */
    enemies;
}

export { GameLogic };

With Enemy undefined (not declared, not imported, or a typo), parseAttributes() returns no attribute and no error; getAttributes() doesn't list the member either. Non-array @type {Enemy} behaves the same, as does @type {{}}.

The type resolves to TypeScript's error type, which has no symbol, so the if (!typeNode) continue at src/parsers/script-parser.ts:500 skips the member before any of the reporting paths below it run. An error type is detectable and could be reported with the existing "…" is not a valid attribute type message.

This is the one that makes a typo'd or missing Interface import hard to diagnose: the attribute just never appears in the Editor, with nothing in the editor's problem list to explain why.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/parsers/script-parser.ts at extractAttributes(), reached through parseAttributes(), and reproduce the three snippets with the playcanvas types loaded as described in test/utils.ts. Trace the neighboring ParsingError paths and add regression coverage for tuple types, empty object initializers, and unresolved @type references. Done means these cases no longer abort or silently disappear, while valid attributes continue to parse.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.