Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"jakarta",
"openrewrite",
"plsql"
]
],
"sonarlint.connectedMode.project": {
"connectionId": "SonarQube for IDE - Visual Studio Code",
"projectKey": "mhagnumdw_rewrite-format-sql"
}
}
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ A set of [OpenRewrite](https://docs.openrewrite.org/) recipes for formatting SQL

- [Recipes](#recipes)
- [FormatSqlBlockRecipe](#formatsqlblockrecipe)
- [FormatSqlTextBlockRecipe](#formatsqltextblockrecipe)
- [FormatSqlFileRecipe](#formatsqlfilerecipe)
- [Configurable Options](#configurable-options)
- [Examples](#examples)
- [FormatSqlBlockRecipe Example](#formatsqlblockrecipe-example)
- [FormatSqlTextBlockRecipe Example](#formatsqltextblockrecipe-example)
- [FormatSqlFileRecipe Example](#formatsqlfilerecipe-example)
- [Usage](#usage)
- [Configuring in `pom.xml`](#configuring-in-pomxml)
Expand All @@ -33,17 +35,23 @@ The `io.github.mhagnumdw.FormatSqlBlockRecipe` recipe automatically formats SQL

> Future enhancements may allow configuration of custom annotations. Please open an issue.

### FormatSqlTextBlockRecipe

The `io.github.mhagnumdw.FormatSqlTextBlockRecipe` recipe formats SQL code in Java [Text Blocks](https://docs.oracle.com/en/java/javase/13/text_blocks/index.html) that are preceded by a `// language=sql` comment (case-insensitive).

This is the same [language injection comment](https://www.jetbrains.com/help/idea/language-injections.html) recognized by IntelliJ IDEA for SQL syntax highlighting.

### FormatSqlFileRecipe

The `io.github.mhagnumdw.FormatSqlFileRecipe` recipe automatically formats the content of SQL files.

## Configurable Options

The following options are applicable to both `FormatSqlBlockRecipe` and `FormatSqlFileRecipe`:
The following options are applicable to `FormatSqlBlockRecipe`, `FormatSqlTextBlockRecipe`, and `FormatSqlFileRecipe`:

| Type | Name | Description | Example | Default Value |
| :------ | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------- |
| String | `filePath` | Optional. The path to the files that the Recipe should process. Accepts a glob expression; multiple patterns can be specified, separated by a semicolon `;`. If omitted, processes all matching files. | `**/*DAO.java` <br> `**/*.sql` | FormatSqlBlockRecipe: `**/*.java` <br> FormatSqlFileRecipe: `**/*.sql` |
| String | `filePath` | Optional. The path to the files that the Recipe should process. Accepts a glob expression; multiple patterns can be specified, separated by a semicolon `;`. If omitted, processes all matching files. | `**/*DAO.java` <br> `**/*.sql` | FormatSqlBlockRecipe: `**/*.java` <br> FormatSqlTextBlockRecipe: `**/*.java` <br> FormatSqlFileRecipe: `**/*.sql` |
| String | `sqlDialect` | Optional. The SQL dialect to be used for formatting. Valid options: `sql` (StandardSql), `mysql`, `postgresql`, `db2`, `plsql` (Oracle PL/SQL), `n1ql` (Couchbase N1QL), `redshift`, `spark`, `tsql` (SQL Server Transact-SQL). Details [here](https://github.com/vertical-blank/sql-formatter). | `plsql` | `sql` |
| String | `indent` | Optional. The string to be used for indentation. | `" "` for 2 spaces <br> `"\t"` for a tab | 4 spaces `" "` |
| Integer | `maxColumnLength` | Optional. The maximum length of a line before the formatter tries to break it. | `100` | `120` |
Expand Down Expand Up @@ -91,6 +99,33 @@ public interface HolidayRepository {
}
```

### FormatSqlTextBlockRecipe Example

Before

```java
// language=sql
private static final String QUERY = """
select * from users u inner join orders o on u.id = o.user_id where u.active = true order by u.name
""";
```

After

```java
// language=sql
private static final String QUERY = """
select
*
from
users u
inner join orders o on u.id = o.user_id
where
u.active = true
order by
u.name""";
```

### FormatSqlFileRecipe Example

Consider the following `example.sql` file:
Expand Down Expand Up @@ -140,6 +175,7 @@ Inside the plugins section, add:
<activeRecipes>
<!-- Add the recipes you want to use here -->
<recipe>io.github.mhagnumdw.FormatSqlBlockRecipe</recipe>
<recipe>io.github.mhagnumdw.FormatSqlTextBlockRecipe</recipe>
<recipe>io.github.mhagnumdw.FormatSqlFileRecipe</recipe>
</activeRecipes>
<failOnDryRunResults>false</failOnDryRunResults>
Expand Down Expand Up @@ -170,6 +206,8 @@ recipeList:
# Add the Recipes you want to use here
- io.github.mhagnumdw.FormatSqlBlockRecipe:
sqlDialect: "plsql"
- io.github.mhagnumdw.FormatSqlTextBlockRecipe:
sqlDialect: "plsql"
- io.github.mhagnumdw.FormatSqlFileRecipe:
sqlDialect: "mysql"
```
Expand All @@ -185,7 +223,7 @@ And change the `<recipe>` tag in `pom.xml` to:
<recipe>io.github.mhagnumdw.FormatSqlCustomConfig</recipe>
```

> As in this example the `FormatSqlCustomConfig` recipe includes both `FormatSqlBlockRecipe` and `FormatSqlFileRecipe` recipes, in `pom.xml` it is only necessary to define the `FormatSqlCustomConfig` recipe.
> As in this example the `FormatSqlCustomConfig` recipe includes both `FormatSqlBlockRecipe`, `FormatSqlTextBlockRecipe` and `FormatSqlFileRecipe` recipes, in `pom.xml` it is only necessary to define the `FormatSqlCustomConfig` recipe.

Then run:

Expand All @@ -201,7 +239,7 @@ This mode is indicated if your intention is to run the recipe only once.

```bash
./mvnw org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.activeRecipes=io.github.mhagnumdw.FormatSqlBlockRecipe,io.github.mhagnumdw.FormatSqlFileRecipe \
-Drewrite.activeRecipes=io.github.mhagnumdw.FormatSqlBlockRecipe,io.github.mhagnumdw.FormatSqlTextBlockRecipe,io.github.mhagnumdw.FormatSqlFileRecipe \
-Drewrite.recipeArtifactCoordinates=io.github.mhagnumdw:rewrite-format-sql:1.0.0
```

Expand Down
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,21 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-17</artifactId>
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/io/github/mhagnumdw/FormatSqlBlockRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
Expand All @@ -11,9 +13,6 @@
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.search.UsesJavaVersion;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;

/**
* A recipe that formats SQL/HQL in Text Blocks within Java source files.
*/
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/io/github/mhagnumdw/FormatSqlBlockVisitor.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package io.github.mhagnumdw;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import io.github.mhagnumdw.processors.Annotations;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;

public class FormatSqlBlockVisitor extends JavaIsoVisitor<ExecutionContext> {

private final Dialect dialect;
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/io/github/mhagnumdw/FormatSqlFileRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
Expand All @@ -10,9 +12,6 @@
import org.openrewrite.Preconditions;
import org.openrewrite.TreeVisitor;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;

/**
* A recipe that formats SQL text files.
*/
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/io/github/mhagnumdw/FormatSqlFileVisitor.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.github.mhagnumdw;

import com.github.vertical_blank.sqlformatter.SqlFormatter;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.SourceFile;
Expand All @@ -9,10 +12,6 @@

import java.util.Objects;

import com.github.vertical_blank.sqlformatter.SqlFormatter;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;

public class FormatSqlFileVisitor extends TreeVisitor<Tree, ExecutionContext> {

private final Dialect dialect;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.mhagnumdw;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jspecify.annotations.Nullable;
Expand All @@ -9,9 +11,6 @@
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;

/**
* Abstract base class for SQL formatting recipes.
* Provides common configuration options and methods for SQL formatting.
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/io/github/mhagnumdw/FormatSqlTextBlockRecipe.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.mhagnumdw;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.FindSourceFiles;
import org.openrewrite.Preconditions;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.search.UsesJavaVersion;

@Value
@EqualsAndHashCode(callSuper = false)
public class FormatSqlTextBlockRecipe extends FormatSqlRecipeAbstract {

private static final String DEFAULT_FILE_PATH = "**/*.java";

@JsonCreator
public FormatSqlTextBlockRecipe(
@Nullable @JsonProperty("filePath") String filePath,
@Nullable @JsonProperty("sqlDialect") String sqlDialect,
@Nullable @JsonProperty("indent") String indent,
@Nullable @JsonProperty("maxColumnLength") Integer maxColumnLength,
@Nullable @JsonProperty("uppercase") Boolean uppercase) {
super(filePath == null ? DEFAULT_FILE_PATH : filePath, sqlDialect, indent, maxColumnLength, uppercase);
}

String displayName = "Format SQL Text Blocks marked with language injection comment";

String description = "Formats SQL code in Java Text Blocks that are preceded by " +
"a '// language=sql' comment (case-insensitive).";

@Override
TreeVisitor<?, ExecutionContext> getFormattingVisitor(
Dialect dialect, FormatConfig formatConfig) {
TreeVisitor<?, ExecutionContext> check = Preconditions.and(
new FindSourceFiles(getFilePath()).getVisitor(),
new UsesJavaVersion<>(13) // Text blocks were introduced as a preview feature in Java 13 and became a standard feature in Java 15
);
return Preconditions.check(check, new FormatSqlTextBlockVisitor(dialect, formatConfig));
}
}
89 changes: 89 additions & 0 deletions src/main/java/io/github/mhagnumdw/FormatSqlTextBlockVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.github.mhagnumdw;

import static org.openrewrite.java.tree.J.Literal;

import com.github.vertical_blank.sqlformatter.core.FormatConfig;
import com.github.vertical_blank.sqlformatter.languages.Dialect;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.Comment;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TextComment;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class FormatSqlTextBlockVisitor extends JavaIsoVisitor<ExecutionContext> {

private static final Pattern LANGUAGE_SQL_PATTERN = Pattern.compile(
"\\s*language\\s*=\\s*sql\\s*", Pattern.CASE_INSENSITIVE
);

private final Dialect dialect;
private final FormatConfig formatConfig;

FormatSqlTextBlockVisitor(Dialect dialect, FormatConfig formatConfig) {
this.dialect = dialect;
this.formatConfig = formatConfig;
}

@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations varDecls, ExecutionContext ctx) {

if (!hasLanguageSqlComment(varDecls)) {
return varDecls;
}

List<J.VariableDeclarations.NamedVariable> variables = varDecls.getVariables();
boolean changed = false;

for (int i = 0; i < variables.size(); i++) {
J.VariableDeclarations.NamedVariable variable = variables.get(i);
Expression initializer = variable.getInitializer();

if (initializer == null || !TextBlockUtil.isTextBlock(initializer)) {
continue;
}

String indentation = getParentIndentation(varDecls) +
TextBlockUtil.getFileIndent(getCursor());

Literal newLiteral = TextBlockUtil.formatTextBlock(
(Literal) initializer, indentation, dialect, formatConfig);

if (newLiteral != null) {
variable = variable.withInitializer(newLiteral);
List<J.VariableDeclarations.NamedVariable> newVariables = new ArrayList<>(variables);
newVariables.set(i, variable);
variables = newVariables;
changed = true;
}
}

if (!changed) {
return varDecls;
}

return varDecls.withVariables(variables);
}

private static boolean hasLanguageSqlComment(J.VariableDeclarations varDecls) {
List<Comment> comments = varDecls.getPrefix().getComments();

for (Comment comment : comments) {
if (comment instanceof TextComment) {
TextComment tc = (TextComment) comment;
if (!tc.isMultiline() && LANGUAGE_SQL_PATTERN.matcher(tc.getText()).matches()) {
return true;
}
}
}
return false;
}

private static String getParentIndentation(J.VariableDeclarations varDecls) {
return varDecls.getPrefix().getIndent();
}
}
Loading