Skip to content

feat: parse parameter decorators for transforms#3001

Open
jtenner wants to merge 8 commits intoAssemblyScript:mainfrom
jtenner:parse-parameter-decorators
Open

feat: parse parameter decorators for transforms#3001
jtenner wants to merge 8 commits intoAssemblyScript:mainfrom
jtenner:parse-parameter-decorators

Conversation

@jtenner
Copy link
Copy Markdown
Contributor

@jtenner jtenner commented Mar 27, 2026

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

  • added AST storage for parameter decorators on ParameterNode
  • added AST storage for explicit-this parameter decorators on FunctionTypeNode
  • taught the shared parameter parser to preserve decorators across function declarations, methods, constructors, function types, function expressions, and parenthesized arrow functions
  • updated the AST serializer to round-trip preserved parameter decorators
  • added a Program validation pass that rejects surviving parameter decorators once per decorated parameter using the full decorator-list range
  • ran that validation after afterInitialize, so transforms can remove the decorators first
  • added parser, compiler, and transform coverage for accepted syntax, delayed rejection, and transform-time removal
  • documented the transform hook timing in the transform-facing API comments and example transforms

Why

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:

  • transforms can read or erase parameter decorators
  • the compiler still rejects them if they remain after transform time
  • public-facing docs do not need to advertise parameter decorators as supported syntax

Impact

  • transform authors can now observe and rewrite parameter decorators before validation
  • end users still get TS1206 if parameter decorators survive to compilation
  • constructor parameter properties continue to receive no special decorator semantics

Validation

  • npm run build
  • npm run test:parser -- parameter-decorators
  • npm run test:compiler -- parameter-decorators --noDiff
  • npm run test:transform

@jtenner jtenner changed the title [codex] Parse parameter decorators for transforms feat: parse parameter decorators for transforms Mar 27, 2026
@jtenner jtenner marked this pull request as ready for review March 27, 2026 04:35
@jtenner
Copy link
Copy Markdown
Contributor Author

jtenner commented Mar 27, 2026

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.

jtenner added 6 commits April 5, 2026 16:31
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.
@jtenner jtenner closed this Apr 5, 2026
@jtenner jtenner force-pushed the parse-parameter-decorators branch from 4ec3371 to 4786023 Compare April 5, 2026 20:33
@jtenner jtenner reopened this Apr 5, 2026
@jtenner
Copy link
Copy Markdown
Contributor Author

jtenner commented Apr 5, 2026

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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why serializeParameterDecorator instead visitParameterDecorator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this. Sorry.


type Callback = (@arg value: i32) => void;
const expression = function(@arg value: i32): void {};
const arrow = (@arg value: i32): void => {};
Copy link
Copy Markdown
Member

@MaxGraey MaxGraey Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added another test file. Should be good to go.

@PaperPrototype
Copy link
Copy Markdown

👀 are you planning on using this for WGSL/GLSL translation?

type: TypeNode,
initializer: Expression | null,
range: Range
range: Range,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i prefer to put range at the end like the other ast.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TS1206 for 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.

Comment on lines +1 to +47
// 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
};

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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");

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +46
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
};
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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");

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +12
/* 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: [
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants