Skip to content

Treat string literal as file/inline directives only when they are at the top #1390

Description

@hi-ogawa

Some minor divergence is discovered while digging in #1336. This is independent transform feature alignment, so the issue is tracked separately here.


3. Directive-Prologue Recognition

Remaining primitive

Recognize module and function directives only when they occur in the corresponding module or function-body directive prologue.

The current module-level hasDirective uses an unrestricted body search at packages/plugin-rsc/src/transforms/utils.ts:5, while inline matchDirective scans all string expression statements in a function block at packages/plugin-rsc/src/transforms/hoist.ts:280. Neither stops after the first non-directive statement, so both can transform an inert string expression that is not a JavaScript directive:

async function value() {
  initialize()
  'use cache' // not a directive
  return read()
}

import './initialize.js'
'use server' // not a directive

The function scan should stop at initialize(), and the module scan should stop at the import. Both misplaced strings must remain untransformed.

Next.js usage and rationale

Next.js parses module and function directives through dedicated directive visitors rather than matching arbitrary string expressions. Its Server Actions DirectiveVisitor is implemented at server_actions.rs:3418.

Error fixture 8 explicitly places "use server" after const x = 1 in a function. Its output leaves the function untransformed, and its diagnostic states that the directive must be at the top of the function body. Error fixture 9 covers the corresponding module-level case after an import. Error fixture 19 is the closest "use cache" case, although it also wraps the misplaced string in parentheses.

This is not mainly diagnostic alignment. A false positive changes an ordinary function into a hoisted, wrapped, and potentially transported callable even though the source did not declare that role. Correct prologue recognition therefore protects the transform selection boundary itself.

Independent boundary

This can be tested with small positive and negative transform fixtures. The minimum generic contract is that a misplaced string does not trigger transformation. Matching Next.js's compile-time diagnostic can remain a separate validation choice. This work does not require hoist-safety diagnostics, method syntax support, generated identity changes, or runtime E2E.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions