diff --git a/src/Handler/SemanticTokens/AstVisitor.php b/src/Handler/SemanticTokens/AstVisitor.php index b660ea1..79bbb13 100644 --- a/src/Handler/SemanticTokens/AstVisitor.php +++ b/src/Handler/SemanticTokens/AstVisitor.php @@ -29,7 +29,7 @@ * Walk the xphp source + AST and emit {@see TokenSpec} entries for the * PHP-shaped surface (keywords, variables, numbers, strings, comments, * class / interface / enum / function / method / property names) plus - * the xphp generic forms (Slice 3, not yet implemented). + * the xphp generic forms (`typeParameter` for each `T`). * * Two passes: * @@ -37,8 +37,8 @@ * tokens are byte-indexed into the ORIGINAL source (not the stripped * buffer), so positions feed directly into {@see PositionMap}. * Emits keywords, variables, numbers, single-quoted strings, - * double-quoted strings (as a single span -- interpolation - * classification is deferred to Slice 4), and comments. Deliberately + * double-quoted strings (as a single span -- finer-grained + * interpolation classification is not yet handled), and comments. Deliberately * skips T_STRING (identifiers) so the AST pass can classify each * identifier into its semantic role without overlap. * @@ -51,9 +51,11 @@ * names -> `function`, PropertyItem names -> `property`, Param * names -> `parameter`. * - * Slice 3 will extend the AST pass to recognise xphp generic - * `ATTR_GENERIC_PARAMS` / `ATTR_GENERIC_ARGS` decorations and emit - * `typeParameter` for every `T` in the 12 audit forms. + * The token pass classifies identifiers inside `<...>` clauses as + * `typeParameter`, and the AST pass reads the enclosing ClassLike's + * `ATTR_GENERIC_PARAMS` decoration to re-classify reified-`T` + * references (`new T()`, `instanceof T`, `T::class`) the token scan + * can't distinguish from ordinary class names. */ final class AstVisitor { @@ -125,7 +127,7 @@ private function collectFromTokens(array &$out, array $reclassifyVariableAt = [] return; } - // Slice 3: state machine tracks whether we're inside a + // State machine tracks whether we're inside a // `<...>` generic clause. `<` opens a clause if (a) the // previous non-trivial token was an identifier (T_STRING), // and (b) the next non-trivial token is an uppercase-starting @@ -478,7 +480,7 @@ public function emit(array &$out, int $originalOffset, int $length, string $type // Length stays in BYTES at this point -- correct for ASCII-only // identifiers (the vast majority of PHP source). LSP wants // UTF-16 code units; for ASCII the two are equal. Non-ASCII - // tokens (e.g. UTF-8 strings) are an edge case Slice 4 covers. + // tokens (e.g. UTF-8 strings) are an edge case not yet handled. $out[] = new TokenSpec( line: $line, startChar: $startChar, diff --git a/src/Handler/XphpCodeActionHandler.php b/src/Handler/XphpCodeActionHandler.php index 450ee93..995ae14 100644 --- a/src/Handler/XphpCodeActionHandler.php +++ b/src/Handler/XphpCodeActionHandler.php @@ -23,21 +23,20 @@ * `textDocument/codeAction` handler. * * Surfaces lightbulb / Alt+Enter quick-fixes for the cursor's - * range and any diagnostics in it. Currently returns an empty - * action list -- this is scaffolding so the LSP capability is - * advertised correctly (clients suppress the lightbulb UI for - * servers that don't advertise it, even if a future fix is - * available). + * range and any diagnostics in it. Merges three providers: + * - {@see ImportCodeActionProvider} -- "Import class" / "Simplify + * FQN" for the unresolved name under the cursor. + * - {@see DiagnosticCodeActionProvider} -- per-diagnostic fixes + * keyed off `$params->context->diagnostics` (e.g. the + * "Did you mean ...?" null/true/false replacements for the + * undefined-bareword diagnostic). + * - {@see OptimizeImportsCodeActionProvider} -- "Optimize imports" + * removing unused `use` statements. * - * Concrete quick-fixes will land in follow-up commits, each tied - * to a specific diagnostic code emitted by the analyzer (e.g. - * "Did you mean ...?" for the undefined-bareword diagnostic from - * commit 47a37fa). Each fix will: - * 1. Inspect `$params->context->diagnostics` for codes it handles. - * 2. Build a CodeAction with a WorkspaceEdit (textEdits) - * OR a Command to execute server-side. - * 3. Optionally defer the heavy lookup to - * `codeAction/resolve` via XphpCodeActionResolveHandler. + * Each provider builds its CodeAction with the WorkspaceEdit + * attached eagerly, so the action is applied directly on accept + * (no `codeAction/resolve` round-trip -- see + * {@see XphpCodeActionResolveHandler}). * * Available since IntelliJ Platform 2023.2 (the codeAction * capability itself); 2024.2 for the `resolve` round-trip. @@ -63,10 +62,11 @@ public function registerCapabiltiies(ServerCapabilities $capabilities): void { $capabilities->codeActionProvider = new CodeActionOptions( // `resolveProvider: true` opts into the - // `codeAction/resolve` round-trip so quick-fixes - // can emit lightweight items up-front and defer - // the actual WorkspaceEdit construction to the - // moment the user accepts the action. + // `codeAction/resolve` round-trip. Today's providers + // attach their WorkspaceEdit eagerly, so the round-trip + // goes unused (see XphpCodeActionResolveHandler) -- the + // flag is kept as a hook for any future fix expensive + // enough to defer its edit until the user accepts. resolveProvider: true, ); } diff --git a/src/Handler/XphpCodeActionResolveHandler.php b/src/Handler/XphpCodeActionResolveHandler.php index 54eb311..7622032 100644 --- a/src/Handler/XphpCodeActionResolveHandler.php +++ b/src/Handler/XphpCodeActionResolveHandler.php @@ -21,11 +21,13 @@ * expensive to do for every potential action the editor's * lightbulb might render. * - * Currently scaffolding -- there are no actions to resolve yet - * (see XphpCodeActionHandler). Once specific quick-fixes land, - * each emits a `data` payload identifying its kind + target, and - * this handler dispatches on that payload to construct the - * WorkspaceEdit. + * Currently a no-op: every action {@see XphpCodeActionHandler} + * returns already carries its WorkspaceEdit eagerly, so the client + * applies the fix directly and never round-trips through + * `codeAction/resolve`. The handler stays registered as a hook + * for any future fix expensive enough to emit a lightweight item + * up-front (with a `data` payload identifying its kind + target) + * and defer WorkspaceEdit construction to here. * * Available since IntelliJ Platform 2024.2. */ @@ -43,10 +45,10 @@ public function methods(): array */ public function resolve(CodeAction $action): Promise { - // No-op for now -- XphpCodeActionHandler emits an empty - // action list, so this method is never called in - // practice. Future commits will add per-kind dispatch - // here. + // No-op -- XphpCodeActionHandler attaches every action's + // WorkspaceEdit eagerly, so the client never round-trips + // here. Future fixes that defer their edit would add + // per-kind dispatch on `$action->data` at this point. return new Success($action); } } diff --git a/src/Handler/XphpSemanticTokensHandler.php b/src/Handler/XphpSemanticTokensHandler.php index cb8e5b5..c046fd7 100644 --- a/src/Handler/XphpSemanticTokensHandler.php +++ b/src/Handler/XphpSemanticTokensHandler.php @@ -30,10 +30,18 @@ * `vscode-languageclient`) render the tokens with their built-in * "semantic" coloring -- no per-editor theme work required. * - * Slice 1 (this commit): handler advertises capability + dispatches - * to {@see AstVisitor}, but the visitor emits no tokens. The - * client receives an empty `data: []` and renders unchanged. - * Subsequent slices extend the visitor. + * The handler advertises the capability and dispatches to + * {@see AstVisitor}, which emits the file's tokens: keywords, + * variables, numbers, strings, comments, declaration names + * (class / interface / enum / method / function / property / + * parameter), and `typeParameter` for every xphp generic `T` -- + * both inside `<...>` clauses and reified-`T` uses (`new T()`, + * `instanceof T`, `T::class`) in generic class bodies. + * + * Remaining limitation: token `length` is byte-counted, so a + * multi-byte identifier is off by the UTF-8/UTF-16 delta (LSP + * wants UTF-16 code units). ASCII identifiers -- the vast + * majority -- are exact. See {@see AstVisitor::emit}. * * Server-capability shape: an ARRAY value, not a class instance. * The phpactor JSON serializer null-strips empty options classes,