/*
* 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 {NamedStyles, Style} from "../style";
import {randomId} from "../uuid";
import {Tree} from "../tree";
import {MarkersKind} from "../markers";
export const JavaScriptStyles = {
IntelliJ: "org.openrewrite.javascript.style.IntelliJ",
}
export const StyleKind = {
SpacesStyle: "org.openrewrite.javascript.style.SpacesStyle",
WrappingAndBracesStyle: "org.openrewrite.javascript.style.WrappingAndBracesStyle",
BlankLinesStyle: "org.openrewrite.javascript.style.BlankLinesStyle",
TabsAndIndentsStyle: "org.openrewrite.javascript.style.TabsAndIndentsStyle",
PrettierStyle: "org.openrewrite.javascript.style.PrettierStyle",
Autodetect: "org.openrewrite.javascript.style.Autodetect"
} as const;
/**
* Style for Prettier-based formatting.
*
* This implements NamedStyles so it can be both:
* - A marker attached to source files (detected from project's .prettierrc)
* - A style that can be passed via the styles parameter or found via getStyle
*
* When this style is present, AutoformatVisitor will use Prettier for formatting
* instead of the built-in formatting visitors.
*/
export interface PrettierStyle extends NamedStyles {
readonly kind: typeof StyleKind.PrettierStyle;
readonly name: "org.openrewrite.javascript.Prettier";
readonly displayName: "Prettier";
readonly description: "Prettier code formatter configuration.";
/**
* The resolved Prettier options for this file (with overrides applied).
*/
readonly config: Record;
/**
* The Prettier version from the project's package.json.
* At formatting time, this version of Prettier will be loaded dynamically
* to ensure consistent formatting.
*/
readonly prettierVersion?: string;
/**
* Whether this file is ignored by .prettierignore.
* When true, Prettier formatting should be skipped for this file.
*/
readonly ignored: boolean;
}
export function prettierStyle(
id: string,
config: Record,
prettierVersion?: string,
ignored: boolean = false
): PrettierStyle {
return {
kind: StyleKind.PrettierStyle,
id,
name: "org.openrewrite.javascript.Prettier",
displayName: "Prettier",
description: "Prettier code formatter configuration.",
tags: [],
styles: [],
config,
prettierVersion,
ignored
};
}
export const SpacesStyleDetailKind = {
SpacesStyleBeforeParentheses: "org.openrewrite.javascript.style.SpacesStyle$BeforeParentheses",
SpacesStyleAroundOperators: "org.openrewrite.javascript.style.SpacesStyle$AroundOperators",
SpacesStyleBeforeLeftBrace: "org.openrewrite.javascript.style.SpacesStyle$BeforeLeftBrace",
SpacesStyleBeforeKeywords: "org.openrewrite.javascript.style.SpacesStyle$BeforeKeywords",
SpacesStyleWithin: "org.openrewrite.javascript.style.SpacesStyle$Within",
SpacesStyleTernaryOperator: "org.openrewrite.javascript.style.SpacesStyle$TernaryOperator",
SpacesStyleOther: "org.openrewrite.javascript.style.SpacesStyle$Other",
} as const;
export interface SpacesStyle extends Style {
readonly kind: typeof StyleKind.SpacesStyle;
readonly beforeParentheses: SpacesStyle.BeforeParentheses;
readonly aroundOperators: SpacesStyle.AroundOperators;
readonly beforeLeftBrace: SpacesStyle.BeforeLeftBrace;
readonly beforeKeywords: SpacesStyle.BeforeKeywords;
readonly within: SpacesStyle.Within;
readonly ternaryOperator: SpacesStyle.TernaryOperator;
readonly other: SpacesStyle.Other;
}
export namespace SpacesStyle {
export interface BeforeParentheses {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleBeforeParentheses;
readonly functionDeclarationParentheses: boolean;
readonly functionCallParentheses: boolean;
readonly ifParentheses: boolean;
readonly forParentheses: boolean;
readonly whileParentheses: boolean;
readonly switchParentheses: boolean;
readonly catchParentheses: boolean;
readonly inFunctionCallExpression: boolean;
readonly inAsyncArrowFunction: boolean;
}
export interface AroundOperators {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleAroundOperators;
readonly assignment: boolean;
readonly logical: boolean;
readonly equality: boolean;
readonly relational: boolean;
readonly bitwise: boolean;
readonly additive: boolean;
readonly multiplicative: boolean;
readonly shift: boolean;
readonly unary: boolean;
readonly arrowFunction: boolean;
readonly beforeUnaryNotAndNotNull: boolean;
readonly afterUnaryNotAndNotNull: boolean;
}
export interface BeforeLeftBrace {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleBeforeLeftBrace;
readonly functionLeftBrace: boolean;
readonly ifLeftBrace: boolean;
readonly elseLeftBrace: boolean;
readonly forLeftBrace: boolean;
readonly whileLeftBrace: boolean;
readonly doLeftBrace: boolean;
readonly switchLeftBrace: boolean;
readonly tryLeftBrace: boolean;
readonly catchLeftBrace: boolean;
readonly finallyLeftBrace: boolean;
readonly classInterfaceModuleLeftBrace: boolean;
}
export interface BeforeKeywords {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleBeforeKeywords;
readonly elseKeyword: boolean;
readonly whileKeyword: boolean;
readonly catchKeyword: boolean;
readonly finallyKeyword: boolean;
}
export interface Within {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleWithin;
readonly indexAccessBrackets: boolean;
readonly groupingParentheses: boolean;
readonly functionDeclarationParentheses: boolean;
readonly functionCallParentheses: boolean;
readonly ifParentheses: boolean;
readonly forParentheses: boolean;
readonly whileParentheses: boolean;
readonly switchParentheses: boolean;
readonly catchParentheses: boolean;
readonly objectLiteralBraces: boolean;
readonly es6ImportExportBraces: boolean;
readonly arrayBrackets: boolean;
readonly interpolationExpressions: boolean;
readonly objectLiteralTypeBraces: boolean;
readonly unionAndIntersectionTypes: boolean;
readonly typeAssertions: boolean;
}
export interface TernaryOperator {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleTernaryOperator;
readonly beforeQuestionMark: boolean;
readonly afterQuestionMark: boolean;
readonly beforeColon: boolean;
readonly afterColon: boolean;
}
export interface Other {
readonly kind: typeof SpacesStyleDetailKind.SpacesStyleOther;
readonly beforeComma: boolean;
readonly afterComma: boolean;
readonly beforeForSemicolon: boolean;
readonly beforePropertyNameValueSeparator: boolean;
readonly afterPropertyNameValueSeparator: boolean;
readonly afterVarArgInRestOrSpread: boolean;
readonly beforeAsteriskInGenerator: boolean;
readonly afterAsteriskInGenerator: boolean;
readonly beforeTypeReferenceColon: boolean;
readonly afterTypeReferenceColon: boolean;
}
}
export const WrappingAndBracesStyleDetailKind = {
WrappingAndBracesStyleIfStatement: "org.openrewrite.javascript.style.WrappingAndBracesStyle$IfStatement",
WrappingAndBracesStyleKeepWhenReformatting: "org.openrewrite.javascript.style.WrappingAndBracesStyle$KeepWhenReformatting",
} as const;
export interface WrappingAndBracesStyle extends Style {
// TODO add more flags; this is what we have in Java, but IntelliJ has way more settings
readonly kind: typeof StyleKind.WrappingAndBracesStyle;
readonly ifStatement: WrappingAndBracesStyle.IfStatement;
readonly keepWhenReformatting: WrappingAndBracesStyle.KeepWhenReformatting;
}
export namespace WrappingAndBracesStyle {
export interface IfStatement {
readonly kind: typeof WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleIfStatement;
readonly elseOnNewLine: boolean;
}
export interface KeepWhenReformatting {
readonly kind: typeof WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleKeepWhenReformatting;
readonly simpleBlocksInOneLine: boolean;
readonly simpleMethodsInOneLine: boolean;
}
}
export interface BlankLinesStyle extends Style {
readonly kind: "org.openrewrite.javascript.style.BlankLinesStyle";
readonly keepMaximum: BlankLinesStyle.KeepMaximum;
readonly minimum: BlankLinesStyle.Minimum;
}
export namespace BlankLinesStyle {
export interface KeepMaximum {
readonly inCode: number;
}
export interface Minimum {
readonly afterImports: number;
readonly aroundClass: number;
readonly aroundFieldInInterface?: number;
readonly aroundField: number;
readonly aroundMethodInInterface?: number;
readonly aroundMethod: number;
readonly aroundFunction: number;
}
}
export interface TabsAndIndentsStyle extends Style {
readonly kind: typeof StyleKind.TabsAndIndentsStyle;
readonly useTabCharacter: boolean;
readonly tabSize: number;
readonly indentSize: number;
readonly continuationIndent: number;
readonly keepIndentsOnEmptyLines: boolean;
readonly indentChainedMethods: boolean;
readonly indentAllChainedCallsInAGroup: boolean;
}
export namespace IntelliJ {
export namespace JavaScript {
export const defaults: NamedStyles = {
kind: JavaScriptStyles.IntelliJ,
id: randomId(),
name: "org.openrewrite.javascript.IntelliJ",
displayName: "IntelliJ IDEA",
description: "Default IntelliJ IDEA code style.",
tags: [],
styles: [spaces(), wrappingAndBraces(), blankLines(), tabsAndIndents()],
}
export function spaces(): SpacesStyle {
return {
kind: StyleKind.SpacesStyle,
beforeParentheses: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeParentheses,
functionDeclarationParentheses: false,
functionCallParentheses: false,
ifParentheses: true,
forParentheses: true,
whileParentheses: true,
switchParentheses: true,
catchParentheses: true,
inFunctionCallExpression: true,
inAsyncArrowFunction: true
},
aroundOperators: {
kind: SpacesStyleDetailKind.SpacesStyleAroundOperators,
assignment: true,
logical: true,
equality: true,
relational: true,
bitwise: true,
additive: true,
multiplicative: true,
shift: true,
unary: false,
arrowFunction: true,
beforeUnaryNotAndNotNull: false,
afterUnaryNotAndNotNull: false
},
beforeLeftBrace: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeLeftBrace,
functionLeftBrace: true,
ifLeftBrace: true,
elseLeftBrace: true,
forLeftBrace: true,
whileLeftBrace: true,
doLeftBrace: true,
switchLeftBrace: true,
tryLeftBrace: true,
catchLeftBrace: true,
finallyLeftBrace: true,
classInterfaceModuleLeftBrace: true
},
beforeKeywords: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeKeywords,
elseKeyword: true,
whileKeyword: true,
catchKeyword: true,
finallyKeyword: true
},
within: {
kind: SpacesStyleDetailKind.SpacesStyleWithin,
indexAccessBrackets: false,
groupingParentheses: false,
functionDeclarationParentheses: false,
functionCallParentheses: false,
ifParentheses: false,
forParentheses: false,
whileParentheses: false,
switchParentheses: false,
catchParentheses: false,
objectLiteralBraces: false,
es6ImportExportBraces: false,
arrayBrackets: false,
interpolationExpressions: false,
objectLiteralTypeBraces: false,
unionAndIntersectionTypes: false,
typeAssertions: false
},
ternaryOperator: {
kind: SpacesStyleDetailKind.SpacesStyleTernaryOperator,
beforeQuestionMark: true,
afterQuestionMark: true,
beforeColon: true,
afterColon: true
},
other: {
kind: SpacesStyleDetailKind.SpacesStyleOther,
beforeComma: false,
afterComma: true,
beforeForSemicolon: false,
beforePropertyNameValueSeparator: false,
afterPropertyNameValueSeparator: true,
afterVarArgInRestOrSpread: false,
beforeAsteriskInGenerator: false,
afterAsteriskInGenerator: true,
beforeTypeReferenceColon: false,
afterTypeReferenceColon: false
},
};
}
export function wrappingAndBraces(): WrappingAndBracesStyle {
return {
kind: StyleKind.WrappingAndBracesStyle,
ifStatement: {
kind: WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleIfStatement,
elseOnNewLine: false
},
keepWhenReformatting: {
kind: WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleKeepWhenReformatting,
simpleBlocksInOneLine: false,
simpleMethodsInOneLine: false
}
};
}
export function blankLines(): BlankLinesStyle {
return {
kind: StyleKind.BlankLinesStyle,
keepMaximum: {
inCode: 2
},
minimum: {
afterImports: 1,
aroundClass: 1,
aroundField: 0,
aroundMethod: 1,
aroundFunction: 1
}
};
}
export function tabsAndIndents(): TabsAndIndentsStyle {
return {
kind: StyleKind.TabsAndIndentsStyle,
useTabCharacter: false,
tabSize: 4,
indentSize: 4,
continuationIndent: 4,
keepIndentsOnEmptyLines: false,
indentChainedMethods: true,
indentAllChainedCallsInAGroup: false
};
}
}
export namespace TypeScript {
export const defaults: NamedStyles = {
kind: JavaScriptStyles.IntelliJ,
id: randomId(),
name: "org.openrewrite.javascript.IntelliJ",
displayName: "IntelliJ IDEA",
description: "Default IntelliJ IDEA code style.",
tags: [],
styles: [spaces(), wrappingAndBraces(), blankLines(), tabsAndIndents()],
}
export function spaces(): SpacesStyle {
return {
kind: StyleKind.SpacesStyle,
beforeParentheses: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeParentheses,
functionDeclarationParentheses: false,
functionCallParentheses: false,
ifParentheses: true,
forParentheses: true,
whileParentheses: true,
switchParentheses: true,
catchParentheses: true,
inFunctionCallExpression: true,
inAsyncArrowFunction: true
},
aroundOperators: {
kind: SpacesStyleDetailKind.SpacesStyleAroundOperators,
assignment: true,
logical: true,
equality: true,
relational: true,
bitwise: true,
additive: true,
multiplicative: true,
shift: true,
unary: false,
arrowFunction: true,
beforeUnaryNotAndNotNull: false,
afterUnaryNotAndNotNull: false
},
beforeLeftBrace: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeLeftBrace,
functionLeftBrace: true,
ifLeftBrace: true,
elseLeftBrace: true,
forLeftBrace: true,
whileLeftBrace: true,
doLeftBrace: true,
switchLeftBrace: true,
tryLeftBrace: true,
catchLeftBrace: true,
finallyLeftBrace: true,
classInterfaceModuleLeftBrace: true
},
beforeKeywords: {
kind: SpacesStyleDetailKind.SpacesStyleBeforeKeywords,
elseKeyword: true,
whileKeyword: true,
catchKeyword: true,
finallyKeyword: true
},
within: {
kind: SpacesStyleDetailKind.SpacesStyleWithin,
indexAccessBrackets: false,
groupingParentheses: false,
functionDeclarationParentheses: false,
functionCallParentheses: false,
ifParentheses: false,
forParentheses: false,
whileParentheses: false,
switchParentheses: false,
catchParentheses: false,
objectLiteralBraces: false,
es6ImportExportBraces: false,
arrayBrackets: false,
interpolationExpressions: false,
objectLiteralTypeBraces: true,
unionAndIntersectionTypes: true,
typeAssertions: false
},
ternaryOperator: {
kind: SpacesStyleDetailKind.SpacesStyleTernaryOperator,
beforeQuestionMark: true,
afterQuestionMark: true,
beforeColon: true,
afterColon: true
},
other: {
kind: SpacesStyleDetailKind.SpacesStyleOther,
beforeComma: false,
afterComma: true,
beforeForSemicolon: false,
beforePropertyNameValueSeparator: false,
afterPropertyNameValueSeparator: true,
afterVarArgInRestOrSpread: false,
beforeAsteriskInGenerator: false,
afterAsteriskInGenerator: true,
beforeTypeReferenceColon: false,
afterTypeReferenceColon: true
},
};
}
export function wrappingAndBraces(): WrappingAndBracesStyle {
return {
kind: StyleKind.WrappingAndBracesStyle,
ifStatement: {
kind: WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleIfStatement,
elseOnNewLine: false
},
keepWhenReformatting: {
kind: WrappingAndBracesStyleDetailKind.WrappingAndBracesStyleKeepWhenReformatting,
simpleBlocksInOneLine: false,
simpleMethodsInOneLine: false
}
};
}
export function blankLines(): BlankLinesStyle {
return {
kind: StyleKind.BlankLinesStyle,
keepMaximum: {
inCode: 2
},
minimum: {
afterImports: 1,
aroundClass: 1,
aroundFieldInInterface: 0,
aroundField: 0,
aroundMethodInInterface: 1,
aroundMethod: 1,
aroundFunction: 1
}
};
}
export function tabsAndIndents(): TabsAndIndentsStyle {
return {
kind: StyleKind.TabsAndIndentsStyle,
useTabCharacter: false,
tabSize: 4,
indentSize: 4,
continuationIndent: 4,
keepIndentsOnEmptyLines: false,
indentChainedMethods: true,
indentAllChainedCallsInAGroup: false
};
}
}
}
export function styleFromSourceFile(styleKind: string, sourceFile: Tree): Style | undefined {
const namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
const candidate = namedStyles.map(namedStyle => namedStyle.styles).flat()
.find(style => style.kind === styleKind)
if (candidate) {
return candidate;
}
return IntelliJ.TypeScript.defaults.styles.find(style => style.kind === styleKind) as Style;
}
/**
* Get a style by kind, with passed-in styles taking precedence over source file styles.
* Falls back to IntelliJ defaults if no style is found.
*
* @param styleKind The kind of style to retrieve
* @param sourceFile The source file to check for styles
* @param styles Optional array of NamedStyles that take precedence over source file styles
*/
export function getStyle(styleKind: string, sourceFile: Tree, styles?: NamedStyles[]): Style | undefined {
// First check passed-in styles (highest precedence)
if (styles) {
for (const namedStyle of styles) {
const found = namedStyle.styles.find(s => s.kind === styleKind);
if (found) {
return found;
}
}
}
// Then check source file markers
const namedStyles = sourceFile.markers.markers.filter(marker => marker.kind === MarkersKind.NamedStyles) as NamedStyles[];
for (const namedStyle of namedStyles) {
const found = namedStyle.styles.find(s => s.kind === styleKind);
if (found) {
return found;
}
}
// Fall back to defaults
return IntelliJ.TypeScript.defaults.styles.find(style => style.kind === styleKind) as Style;
}