/* * Copyright 2025 the original author or authors. *

* Licensed under the Moderne Source Available License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://docs.moderne.io/licensing/moderne-source-available-license *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {Marker} from "../markers"; import {J} from "../java"; import {JS} from "./tree"; import {RpcCodecs, RpcReceiveQueue, RpcSendQueue} from "../rpc"; import { PrettierStyle, SpacesStyle, SpacesStyleDetailKind, StyleKind, TabsAndIndentsStyle, WrappingAndBracesStyle, WrappingAndBracesStyleDetailKind } from "./style"; import {Autodetect} from "./autodetect"; import {updateIfChanged} from "../util"; declare module "./tree" { namespace JS { export const Markers: { readonly Generator: "org.openrewrite.javascript.marker.Generator"; readonly Optional: "org.openrewrite.javascript.marker.Optional"; readonly NonNullAssertion: "org.openrewrite.javascript.marker.NonNullAssertion"; readonly DelegatedYield: "org.openrewrite.javascript.marker.DelegatedYield"; readonly FunctionDeclaration: "org.openrewrite.javascript.marker.FunctionDeclaration"; }; } } // At runtime actually attach it to JS (JS as any).Markers = { Generator: "org.openrewrite.javascript.marker.Generator", DelegatedYield: "org.openrewrite.javascript.marker.DelegatedYield", Optional: "org.openrewrite.javascript.marker.Optional", NonNullAssertion: "org.openrewrite.javascript.marker.NonNullAssertion", FunctionDeclaration: "org.openrewrite.javascript.marker.FunctionDeclaration", } as const; /** * The `yield*` operator in JavaScript is a special case of the `yield` operator, * used in generator functions to yield to another iterator. This marker will * be applied to a {@link J.Yield} expression. */ export interface DelegatedYield extends Marker { readonly kind: typeof JS.Markers.DelegatedYield; readonly prefix: J.Space; } export interface Optional extends Marker { readonly kind: typeof JS.Markers.Optional; readonly prefix: J.Space; } export interface Generator extends Marker { readonly kind: typeof JS.Markers.Generator; readonly prefix: J.Space; } export interface NonNullAssertion extends Marker { readonly kind: typeof JS.Markers.NonNullAssertion; readonly prefix: J.Space; } export interface FunctionDeclaration extends Marker { readonly kind: typeof JS.Markers.FunctionDeclaration; readonly prefix: J.Space; } /** * Registers an RPC codec for any marker that has a `prefix: J.Space` field. */ function registerPrefixedMarkerCodec( kind: M["kind"] ) { RpcCodecs.registerCodec(kind, { async rpcReceive(before: M, q: RpcReceiveQueue): Promise { return updateIfChanged(before, { id: await q.receive(before.id), prefix: await q.receive(before.prefix) } as Partial); }, async rpcSend(after: M, q: RpcSendQueue): Promise { await q.getAndSend(after, a => a.id); await q.getAndSend(after, a => a.prefix); } }); } registerPrefixedMarkerCodec(JS.Markers.DelegatedYield); registerPrefixedMarkerCodec(JS.Markers.Optional); registerPrefixedMarkerCodec(JS.Markers.Generator); registerPrefixedMarkerCodec(JS.Markers.NonNullAssertion); registerPrefixedMarkerCodec(JS.Markers.FunctionDeclaration); // Register codec for PrettierStyle (a NamedStyles that contains Prettier configuration) // Only serialize the variable fields; constant fields are defined in the interface RpcCodecs.registerCodec(StyleKind.PrettierStyle, { async rpcReceive(before: PrettierStyle, q: RpcReceiveQueue): Promise { return updateIfChanged(before, { id: await q.receive(before.id), config: await q.receive(before.config), prettierVersion: await q.receive(before.prettierVersion), ignored: await q.receive(before.ignored), }); }, async rpcSend(after: PrettierStyle, q: RpcSendQueue): Promise { await q.getAndSend(after, a => a.id); await q.getAndSend(after, a => a.config); await q.getAndSend(after, a => a.prettierVersion); await q.getAndSend(after, a => a.ignored); } }); // Register codec for Autodetect (auto-detected styles for JavaScript/TypeScript) // Only serialize the variable fields (id, styles); constant fields are defined in the interface RpcCodecs.registerCodec(StyleKind.Autodetect, { async rpcReceive(before: Autodetect, q: RpcReceiveQueue): Promise { return updateIfChanged(before, { id: await q.receive(before.id), styles: (await q.receiveList(before.styles))!, }); }, async rpcSend(after: Autodetect, q: RpcSendQueue): Promise { await q.getAndSend(after, a => a.id); await q.getAndSendList(after, a => a.styles, s => s.kind); } }); // ============================================================================ // Style RPC Codecs // ============================================================================ // Helper to create a simple codec for objects where all fields are primitives function registerSimpleCodec( kind: T["kind"], fields: (keyof T)[] ) { RpcCodecs.registerCodec(kind, { async rpcReceive(before: T, q: RpcReceiveQueue): Promise { const updates: Partial = {}; for (const field of fields) { (updates as any)[field] = await q.receive((before as any)[field]); } return updateIfChanged(before, updates); }, async rpcSend(after: T, q: RpcSendQueue): Promise { for (const field of fields) { await q.getAndSend(after, a => (a as any)[field]); } } }); } // TabsAndIndentsStyle - simple style with primitive fields registerSimpleCodec(StyleKind.TabsAndIndentsStyle, [ 'useTabCharacter', 'tabSize', 'indentSize', 'continuationIndent', 'keepIndentsOnEmptyLines', 'indentChainedMethods', 'indentAllChainedCallsInAGroup' ]); // SpacesStyle and its nested types registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleBeforeParentheses, [ 'functionDeclarationParentheses', 'functionCallParentheses', 'ifParentheses', 'forParentheses', 'whileParentheses', 'switchParentheses', 'catchParentheses', 'inFunctionCallExpression', 'inAsyncArrowFunction' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleAroundOperators, [ 'assignment', 'logical', 'equality', 'relational', 'bitwise', 'additive', 'multiplicative', 'shift', 'unary', 'arrowFunction', 'beforeUnaryNotAndNotNull', 'afterUnaryNotAndNotNull' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleBeforeLeftBrace, [ 'functionLeftBrace', 'ifLeftBrace', 'elseLeftBrace', 'forLeftBrace', 'whileLeftBrace', 'doLeftBrace', 'switchLeftBrace', 'tryLeftBrace', 'catchLeftBrace', 'finallyLeftBrace', 'classInterfaceModuleLeftBrace' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleBeforeKeywords, [ 'elseKeyword', 'whileKeyword', 'catchKeyword', 'finallyKeyword' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleWithin, [ 'indexAccessBrackets', 'groupingParentheses', 'functionDeclarationParentheses', 'functionCallParentheses', 'ifParentheses', 'forParentheses', 'whileParentheses', 'switchParentheses', 'catchParentheses', 'objectLiteralBraces', 'es6ImportExportBraces', 'arrayBrackets', 'interpolationExpressions', 'objectLiteralTypeBraces', 'unionAndIntersectionTypes', 'typeAssertions' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleTernaryOperator, [ 'beforeQuestionMark', 'afterQuestionMark', 'beforeColon', 'afterColon' ]); registerSimpleCodec(SpacesStyleDetailKind.SpacesStyleOther, [ 'beforeComma', 'afterComma', 'beforeForSemicolon', 'beforePropertyNameValueSeparator', 'afterPropertyNameValueSeparator', 'afterVarArgInRestOrSpread', 'beforeAsteriskInGenerator', 'afterAsteriskInGenerator', 'beforeTypeReferenceColon', 'afterTypeReferenceColon' ]); // SpacesStyle - has nested objects RpcCodecs.registerCodec(StyleKind.SpacesStyle, { async rpcReceive(before: SpacesStyle, q: RpcReceiveQueue): Promise { return updateIfChanged(before, { beforeParentheses: await q.receive(before.beforeParentheses), aroundOperators: await q.receive(before.aroundOperators), beforeLeftBrace: await q.receive(before.beforeLeftBrace), beforeKeywords: await q.receive(before.beforeKeywords), within: await q.receive(before.within), ternaryOperator: await q.receive(before.ternaryOperator), other: await q.receive(before.other), }); }, async rpcSend(after: SpacesStyle, q: RpcSendQueue): Promise { await q.getAndSend(after, a => a.beforeParentheses); await q.getAndSend(after, a => a.aroundOperators); await q.getAndSend(after, a => a.beforeLeftBrace); await q.getAndSend(after, a => a.beforeKeywords); await q.getAndSend(after, a => a.within); await q.getAndSend(after, a => a.ternaryOperator); await q.getAndSend(after, a => a.other); } }); // WrappingAndBracesStyle and its nested types registerSimpleCodec( WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleIfStatement, ['elseOnNewLine'] ); registerSimpleCodec( WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleKeepWhenReformatting, ['simpleBlocksInOneLine', 'simpleMethodsInOneLine'] ); RpcCodecs.registerCodec(StyleKind.WrappingAndBracesStyle, { async rpcReceive(before: WrappingAndBracesStyle, q: RpcReceiveQueue): Promise { return updateIfChanged(before, { ifStatement: await q.receive(before.ifStatement), keepWhenReformatting: await q.receive(before.keepWhenReformatting), }); }, async rpcSend(after: WrappingAndBracesStyle, q: RpcSendQueue): Promise { await q.getAndSend(after, a => a.ifStatement); await q.getAndSend(after, a => a.keepWhenReformatting); } });