feat: parse parameter decorators for transforms#3001
feat: parse parameter decorators for transforms#3001jtenner wants to merge 8 commits intoAssemblyScript:mainfrom
Conversation
|
Whoops! I forgot to check the validation scripts locally. Everything looks good now. I hope this contribution finds it's way to main because I want to use it to make some very cheeky transforms for my webgpu implementation. |
Extend parameter AST nodes to retain decorators, including explicit-this decorators on function signatures, without forcing a broad constructor API churn. Teach both parameter parsers to accept stacked decorators on regular, rest, explicit-this, constructor-property, function-type, and parenthesized-arrow parameters. Update the AST builder to serialize parameter decorators inline so parser fixtures can round-trip the new syntax faithfully. Add a focused parser fixture covering the preserved syntax surface before deferred validation is introduced.
Add a Program-owned validation pass that walks the final AST and reports TS1206 once per decorated parameter, using the full decorator-list span for the diagnostic range. Invoke that validation from compile after transforms have had their afterInitialize window, so transformers can still remove parameter decorators before any diagnostics are emitted. Add a dedicated compiler rejection fixture covering regular, rest, explicit-this, constructor-property, function-type, function-expression, and arrow-parameter cases.
Add a dedicated transform input containing the same invalid parameter-decorator forms exercised by the compiler rejection fixture. Introduce ESM and CommonJS afterInitialize transforms that walk the AST and strip parameter decorators, including explicit-this decorators on function signatures. Extend the transform test scripts to compile that input with the stripping transforms, proving no TS1206 diagnostics are emitted once transforms remove the decorators in time.
4ec3371 to
4786023
Compare
|
Is this all set? I rebased it on main and had to add the eslint "ignore" file because of the decorator proof. I left a comment to explain why. Please let me know if anything isn't up to standard! |
src/extra/ast.ts
Outdated
| } | ||
| } | ||
|
|
||
| private serializeParameterDecorator(node: DecoratorNode): void { |
There was a problem hiding this comment.
Why serializeParameterDecorator instead visitParameterDecorator?
There was a problem hiding this comment.
I missed this. Sorry.
|
|
||
| type Callback = (@arg value: i32) => void; | ||
| const expression = function(@arg value: i32): void {}; | ||
| const arrow = (@arg value: i32): void => {}; |
There was a problem hiding this comment.
You also need to add sample test cases and handle errors properly. Someting like:
function regularEmpty(@first): void {}
function firstEmpty(@first, value: i32): void {}
function regularWrongPos(value @first: i32): void {}
function regularWrongPos2(value: i32 @first): void {}
function restWrongPos(...values @rest: i32[]): void {}
function selfWrongPos(this: i32 @self): void {}
function noLits(@123 value: i32): void {}
function noExprs(@(a + b) value: i32): void {}There was a problem hiding this comment.
I added another test file. Should be good to go.
|
👀 are you planning on using this for WGSL/GLSL translation? |
| type: TypeNode, | ||
| initializer: Expression | null, | ||
| range: Range | ||
| range: Range, |
There was a problem hiding this comment.
i prefer to put range at the end like the other ast.
There was a problem hiding this comment.
Pull request overview
Enables parsing and AST preservation of parameter decorators (including decorators on explicit this) so transforms can inspect/remove them before compilation-time validation, while keeping parameter decorators invalid if they survive past transform time.
Changes:
- Extend the AST (
ParameterNode,FunctionTypeNode) and serializer to round-trip preserved parameter decorators. - Update the parser to accept/preserve parameter decorators across function declarations/expressions, methods, constructors, and function types (including explicit
this). - Add a post-transform validation pass that emits
TS1206for any remaining parameter decorators, plus parser/compiler/transform tests and docs timing updates.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/ast.ts |
Adds storage for parameter decorators on ParameterNode and explicit-this decorators on FunctionTypeNode. |
src/parser.ts |
Parses and preserves parameter decorators in parameter lists and function types; threads explicit-this decorators through function expression parsing. |
src/extra/ast.ts |
Serializes preserved parameter decorators so they can be round-tripped. |
src/program.ts |
Implements a one-shot AST traversal to report surviving parameter decorators after transforms. |
src/compiler.ts |
Runs the new post-transform parameter-decorator validation after initialization. |
src/index-wasm.ts |
Clarifies initializeProgram timing for transforms relative to validation. |
cli/index.js |
Documents afterInitialize as the last AST rewrite point before compilation-time validation. |
cli/index.d.ts |
Updates transform API docs to clarify afterInitialize timing and intent. |
tests/parser/parameter-decorators.ts |
Adds parser coverage for parameter decorator syntax across constructs. |
tests/parser/parameter-decorators.ts.fixture.ts |
Adds parser fixture inputs covering multiple decorators, this, and rest parameters. |
tests/compiler/parameter-decorators.ts |
Adds compiler test ensuring TS1206 is emitted if decorators survive. |
tests/compiler/parameter-decorators.json |
Expected stderr for surviving parameter decorator validation. |
tests/compiler/parameter-decorators-errors.ts |
Adds invalid-syntax compiler coverage for decorator placement/forms. |
tests/compiler/parameter-decorators-errors.json |
Expected stderr for invalid parameter decorator syntax cases. |
tests/transform/parameter-decorators.ts |
Transform test input program using parameter decorators (including on this). |
tests/transform/remove-parameter-decorators.js |
ESM transform that strips preserved parameter decorators during afterInitialize. |
tests/transform/cjs/remove-parameter-decorators.js |
CommonJS transform that strips preserved parameter decorators during afterInitialize. |
package.json |
Extends transform test scripts to cover decorator stripping behavior in ESM+CJS transforms. |
eslint.config.js |
Ignores the transform fixture that is intentionally invalid TypeScript syntax. |
.eslintrc.cjs |
Adds a legacy ESLint config file (in addition to existing flat config). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Example transform proving that preserved parameter decorators can be stripped | ||
| // during afterInitialize before compilation rejects them. | ||
| console.log("Parameter decorator removal transform loaded"); | ||
|
|
||
| const NodeKind = { | ||
| NamedType: 1, | ||
| FunctionType: 2, | ||
| Assertion: 7, | ||
| Binary: 8, | ||
| Call: 9, | ||
| Class: 10, | ||
| Comma: 11, | ||
| ElementAccess: 12, | ||
| Function: 14, | ||
| InstanceOf: 15, | ||
| Literal: 16, | ||
| New: 17, | ||
| Parenthesized: 20, | ||
| PropertyAccess: 21, | ||
| Ternary: 22, | ||
| UnaryPostfix: 27, | ||
| UnaryPrefix: 28, | ||
| Block: 30, | ||
| Do: 33, | ||
| ExportDefault: 36, | ||
| Expression: 38, | ||
| For: 39, | ||
| ForOf: 40, | ||
| If: 41, | ||
| Return: 43, | ||
| Switch: 44, | ||
| Throw: 45, | ||
| Try: 46, | ||
| Variable: 47, | ||
| Void: 48, | ||
| While: 49, | ||
| ClassDeclaration: 51, | ||
| EnumDeclaration: 52, | ||
| FieldDeclaration: 54, | ||
| FunctionDeclaration: 55, | ||
| InterfaceDeclaration: 57, | ||
| MethodDeclaration: 58, | ||
| NamespaceDeclaration: 59, | ||
| TypeDeclaration: 60, | ||
| VariableDeclaration: 61 | ||
| }; | ||
|
|
There was a problem hiding this comment.
NodeKind/LiteralKind are hard-coded as numeric constants. This is brittle: any change to the compiler’s NodeKind enum ordering will silently break this transform and the transform test. Prefer importing the enum values from the AssemblyScript transform API (ESM) so the test follows the actual runtime values.
| // Example transform proving that preserved parameter decorators can be stripped | |
| // during afterInitialize before compilation rejects them. | |
| console.log("Parameter decorator removal transform loaded"); | |
| const NodeKind = { | |
| NamedType: 1, | |
| FunctionType: 2, | |
| Assertion: 7, | |
| Binary: 8, | |
| Call: 9, | |
| Class: 10, | |
| Comma: 11, | |
| ElementAccess: 12, | |
| Function: 14, | |
| InstanceOf: 15, | |
| Literal: 16, | |
| New: 17, | |
| Parenthesized: 20, | |
| PropertyAccess: 21, | |
| Ternary: 22, | |
| UnaryPostfix: 27, | |
| UnaryPrefix: 28, | |
| Block: 30, | |
| Do: 33, | |
| ExportDefault: 36, | |
| Expression: 38, | |
| For: 39, | |
| ForOf: 40, | |
| If: 41, | |
| Return: 43, | |
| Switch: 44, | |
| Throw: 45, | |
| Try: 46, | |
| Variable: 47, | |
| Void: 48, | |
| While: 49, | |
| ClassDeclaration: 51, | |
| EnumDeclaration: 52, | |
| FieldDeclaration: 54, | |
| FunctionDeclaration: 55, | |
| InterfaceDeclaration: 57, | |
| MethodDeclaration: 58, | |
| NamespaceDeclaration: 59, | |
| TypeDeclaration: 60, | |
| VariableDeclaration: 61 | |
| }; | |
| import { NodeKind } from "assemblyscript/dist/assemblyscript.js"; | |
| // Example transform proving that preserved parameter decorators can be stripped | |
| // during afterInitialize before compilation rejects them. | |
| console.log("Parameter decorator removal transform loaded"); |
| const NodeKind = { | ||
| NamedType: 1, | ||
| FunctionType: 2, | ||
| Assertion: 7, | ||
| Binary: 8, | ||
| Call: 9, | ||
| Class: 10, | ||
| Comma: 11, | ||
| ElementAccess: 12, | ||
| Function: 14, | ||
| InstanceOf: 15, | ||
| Literal: 16, | ||
| New: 17, | ||
| Parenthesized: 20, | ||
| PropertyAccess: 21, | ||
| Ternary: 22, | ||
| UnaryPostfix: 27, | ||
| UnaryPrefix: 28, | ||
| Block: 30, | ||
| Do: 33, | ||
| ExportDefault: 36, | ||
| Expression: 38, | ||
| For: 39, | ||
| ForOf: 40, | ||
| If: 41, | ||
| Return: 43, | ||
| Switch: 44, | ||
| Throw: 45, | ||
| Try: 46, | ||
| Variable: 47, | ||
| Void: 48, | ||
| While: 49, | ||
| ClassDeclaration: 51, | ||
| EnumDeclaration: 52, | ||
| FieldDeclaration: 54, | ||
| FunctionDeclaration: 55, | ||
| InterfaceDeclaration: 57, | ||
| MethodDeclaration: 58, | ||
| NamespaceDeclaration: 59, | ||
| TypeDeclaration: 60, | ||
| VariableDeclaration: 61 | ||
| }; |
There was a problem hiding this comment.
NodeKind/LiteralKind are hard-coded as numeric constants. This is brittle: any change to the compiler’s NodeKind enum ordering will silently break this transform and the transform test. Prefer importing the enum values from the AssemblyScript transform API (CommonJS) so the test follows the actual runtime values.
| const NodeKind = { | |
| NamedType: 1, | |
| FunctionType: 2, | |
| Assertion: 7, | |
| Binary: 8, | |
| Call: 9, | |
| Class: 10, | |
| Comma: 11, | |
| ElementAccess: 12, | |
| Function: 14, | |
| InstanceOf: 15, | |
| Literal: 16, | |
| New: 17, | |
| Parenthesized: 20, | |
| PropertyAccess: 21, | |
| Ternary: 22, | |
| UnaryPostfix: 27, | |
| UnaryPrefix: 28, | |
| Block: 30, | |
| Do: 33, | |
| ExportDefault: 36, | |
| Expression: 38, | |
| For: 39, | |
| ForOf: 40, | |
| If: 41, | |
| Return: 43, | |
| Switch: 44, | |
| Throw: 45, | |
| Try: 46, | |
| Variable: 47, | |
| Void: 48, | |
| While: 49, | |
| ClassDeclaration: 51, | |
| EnumDeclaration: 52, | |
| FieldDeclaration: 54, | |
| FunctionDeclaration: 55, | |
| InterfaceDeclaration: 57, | |
| MethodDeclaration: 58, | |
| NamespaceDeclaration: 59, | |
| TypeDeclaration: 60, | |
| VariableDeclaration: 61 | |
| }; | |
| const { NodeKind, LiteralKind } = require("assemblyscript/dist/assemblyscript.js"); |
| /* global module */ | ||
|
|
||
| module.exports = { | ||
| root: true, | ||
| ignorePatterns: [ | ||
| // These fixtures intentionally exercise AssemblyScript-only syntax that | ||
| // TypeScript's parser rejects before lint rules can run. | ||
| "tests/compiler/parameter-decorators.ts", | ||
| "tests/transform/parameter-decorators.ts" | ||
| ], | ||
| parser: "@typescript-eslint/parser", | ||
| plugins: [ |
There was a problem hiding this comment.
This PR adds a full legacy .eslintrc.cjs alongside the existing flat eslint.config.js configuration. With ESLint v10 (as pinned in package.json), eslint.config.js is the active config and .eslintrc.* is typically ignored unless users opt out of flat config. Consider removing this file or documenting why it’s needed to avoid confusing contributors with two independent ESLint configurations.
Summary
This PR makes parameter decorators parseable and preservable in the AST so transforms can inspect or remove them before compilation-time validation runs.
This is intentionally not a new end-user language feature. Any parameter decorators that survive transform time still produce
TS1206: Decorators are not valid here.What Changed
ParameterNodethisparameter decorators onFunctionTypeNodeProgramvalidation pass that rejects surviving parameter decorators once per decorated parameter using the full decorator-list rangeafterInitialize, so transforms can remove the decorators firstWhy
The parser and transform pipeline can use preserved parameter decorators as an AST-level extension point without committing AssemblyScript to supporting them as regular language syntax.
That gives transform authors a useful hook while keeping the language semantics unchanged:
Impact
TS1206if parameter decorators survive to compilationValidation
npm run buildnpm run test:parser -- parameter-decoratorsnpm run test:compiler -- parameter-decorators --noDiffnpm run test:transform