diff --git a/.editorconfig b/.editorconfig index 75d15515..a3f7199f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1 +1,4 @@ -[*.{kt,kts}] \ No newline at end of file +[*.{kt,kts}] + +[src/test/testData/sql/**.sql] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/README.md b/README.md index 87aa93d8..570c5571 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,14 @@ Along with the Dao name change, we will refactor the SQL file directory and file - After refactoring the Dao method name, we will also change the SQL file name. - After refactoring the Dao package, we will also change the SQL directory. +## Formatter(Preview) +Provides code formatting for SQL syntax. + +This feature is in preview. You cannot customize the indentation or keywords to be broken down! + +You can reformat entire directories and files with "Code > Reformat Code". + +If you want to enable the plugin's formatting function, check "Enable SQL Format" in "Settings > Other Settings > Doma Tools". (The default is OFF.) ## Settings Some functions of "Doma Tools" can be customized from the settings screen. diff --git a/src/main/java/org/domaframework/doma/intellij/Sql.bnf b/src/main/java/org/domaframework/doma/intellij/Sql.bnf index 308a270b..91750ea7 100644 --- a/src/main/java/org/domaframework/doma/intellij/Sql.bnf +++ b/src/main/java/org/domaframework/doma/intellij/Sql.bnf @@ -33,30 +33,30 @@ EL_POPULATE = "%populate" EL_PARSER_LEVEL_COMMENT = "%!" EL_END = "%end" - EL_HASH = "#" - EL_CARET = "^" - EL_SEPARATOR = ":" - EL_NULL = "null" - EL_BOOLEAN = "regexp:(true|false)" - EL_DOT = "." - EL_PLUS = "+" - EL_MINUS = "-" - EL_ASTERISK = "*" - EL_SLASH = "/" - EL_PERCENT = "%" + HASH = "#" + CARET = "^" + SEPARATOR = ":" + NULL = "null" + BOOLEAN = "regexp:(true|false)" + DOT = "." + PLUS = "+" + MINUS = "-" + ASTERISK = "*" + SLASH = "/" + PERCENT = "%" EL_EQ = "==" EL_NE = "!=" - EL_LT = "<" - EL_LE = "<=" - EL_GT = ">" - EL_GE = ">=" + LT = "<" + LE = "<=" + GT = ">" + GE = ">=" EL_NOT = "!" EL_AND = "&&" EL_OR = "||" - EL_COMMA = "," - EL_LEFT_PAREN = "(" - EL_RIGHT_PAREN = ")" - EL_AT_SIGN = "@" + COMMA = "," + LEFT_PAREN = "(" + RIGHT_PAREN = ")" + AT_SIGN = "@" EL_NEW = "new" EL_NUMBER = "regexp:\d+(L|(\.\d+)?[FDB])?" EL_STRING = "regexp:\"([^\"\\]|\\\"|\\)*\"" @@ -74,10 +74,26 @@ sql_file ::= content * private content ::= !<> item {pin=1 recoverWhile=content_recover} -private item ::= (comment | literal | word | OTHER) +private item ::= (comment | literal | word | + OTHER | + DOT | + COMMA | + ASTERISK | + LEFT_PAREN | + RIGHT_PAREN | + PLUS | + MINUS | + SLASH | + PERCENT | + LE | + GE | + LT | + GT | + NULL | + BOOLEAN ) private comment ::= (block_comment | LINE_COMMENT) private literal ::= (STRING | NUMBER) -private word ::= (KEYWORD | WORD) +private word ::= (KEYWORD | WORD | DATATYPE) block_comment ::= "/*" (el_directive | BLOCK_COMMENT_CONTENT?) "*/" { pin=1 mixin="org.domaframework.doma.intellij.psi.SqlElCommentExprImpl" @@ -149,6 +165,6 @@ el_parameters ::= "(" (el_expr ("," el_expr)*)? ")" {pin=1} // primary el_primary_expr ::= el_literal_expr | el_id_expr | el_paren_expr -private el_literal_expr ::= EL_NULL | EL_BOOLEAN | (EL_PLUS | EL_MINUS)? EL_NUMBER | EL_STRING | EL_CHAR +private el_literal_expr ::= EL_NULL | BOOLEAN | (EL_PLUS | EL_MINUS)? EL_NUMBER | EL_STRING | EL_CHAR private el_id_expr ::= EL_IDENTIFIER private el_paren_expr ::= "(" el_expr ")" {pin=1} \ No newline at end of file diff --git a/src/main/java/org/domaframework/doma/intellij/Sql.flex b/src/main/java/org/domaframework/doma/intellij/Sql.flex index 010ecc0a..95b48860 100644 --- a/src/main/java/org/domaframework/doma/intellij/Sql.flex +++ b/src/main/java/org/domaframework/doma/intellij/Sql.flex @@ -21,14 +21,23 @@ import org.domaframework.doma.intellij.psi.SqlTypes; %{ // SQL keywords private static final Set KEYWORDS = Set.of( + "add", + "after", "alter", "all", "and", + "as", "asc", + "between", "by", "case", + "change", "check", + "column", + "comment", "create", + "cross", + "database", "default", "delete", "desc", @@ -36,16 +45,20 @@ import org.domaframework.doma.intellij.psi.SqlTypes; "drop", "else", "end", + "except", "exists", + "first", "foreign", "from", "full", "group", "having", + "if", "in", "index", "inner", "insert", + "intersect", "into", "is", "join", @@ -55,28 +68,68 @@ import org.domaframework.doma.intellij.psi.SqlTypes; "limit", "not", "null", + "modify", "offset", "on", + "outer", "or", "order", "primary", "references", + "rename", "right", "select", "set", "table", + "temporary", "then", + "to", + "truncate", "union", "unique", "update", "values", + "view", "when", "where" ); + // COLUMN DataTypes + private static final Set DATATYPES = Set.of( + "int", + "integer", + "smallint", + "bigint", + "tinyint", + "float", + "double", + "decimal", + "numeric", + "char", + "varchar", + "text", + "date", + "time", + "timestamp", + "datetime", + "boolean", + "bit", + "binary", + "varbinary", + "blob", + "clob", + "json", + "enum", + "set" + ); + private static boolean isKeyword(CharSequence word) { return KEYWORDS.contains(word.toString().toLowerCase()); } + + private static boolean isColumnDataType(CharSequence word) { + return DATATYPES.contains(word.toString().toLowerCase()); + } %} %eof{ return; @@ -113,37 +166,52 @@ El_NonWordPart = [=<>\-,/*();\R \n\t\f] {LineComment} { return SqlTypes.LINE_COMMENT; } {String} { return SqlTypes.STRING; } {Number} { return SqlTypes.NUMBER; } - {Word} { return isKeyword(yytext()) ? SqlTypes.KEYWORD : SqlTypes.WORD; } + {Word} { return isKeyword(yytext()) ? SqlTypes.KEYWORD : isColumnDataType(yytext()) ? SqlTypes.DATATYPE : SqlTypes.WORD; } + "." { return SqlTypes.DOT; } + "," { return SqlTypes.COMMA; } + "+" { return SqlTypes.PLUS;} + "-" { return SqlTypes.MINUS;} + "*" { return SqlTypes.ASTERISK;} + "/" { return SqlTypes.SLASH;} + "%" { return SqlTypes.PERCENT;} + "(" { return SqlTypes.LEFT_PAREN; } + ")" { return SqlTypes.RIGHT_PAREN; } + "<" { return SqlTypes.LT;} + "<=" { return SqlTypes.LE;} + ">" { return SqlTypes.GT;} + ">=" { return SqlTypes.GE;} + "true" { return SqlTypes.BOOLEAN;} + "false" { return SqlTypes.BOOLEAN;} ({LineTerminator}|{WhiteSpace})+ { return TokenType.WHITE_SPACE; } [^] { return SqlTypes.OTHER; } } { {BlockCommentEnd} { yybegin(YYINITIAL); return SqlTypes.BLOCK_COMMENT_END; } - ":" { return SqlTypes.EL_SEPARATOR; } - "." { return SqlTypes.EL_DOT; } - "," { return SqlTypes.EL_COMMA; } - "(" { return SqlTypes.EL_LEFT_PAREN; } - ")" { return SqlTypes.EL_RIGHT_PAREN; } - "@" { return SqlTypes.EL_AT_SIGN; } - "+" { return SqlTypes.EL_PLUS;} - "-" { return SqlTypes.EL_MINUS;} - "*" { return SqlTypes.EL_ASTERISK;} - "/" { return SqlTypes.EL_SLASH;} - "%" { return SqlTypes.EL_PERCENT;} + ":" { return SqlTypes.SEPARATOR; } + "." { return SqlTypes.DOT; } + "," { return SqlTypes.COMMA; } + "(" { return SqlTypes.LEFT_PAREN; } + ")" { return SqlTypes.RIGHT_PAREN; } + "@" { return SqlTypes.AT_SIGN; } + "+" { return SqlTypes.PLUS;} + "-" { return SqlTypes.MINUS;} + "*" { return SqlTypes.ASTERISK;} + "/" { return SqlTypes.SLASH;} + "%" { return SqlTypes.PERCENT;} "==" { return SqlTypes.EL_EQ;} "!=" { return SqlTypes.EL_NE;} - "<" { return SqlTypes.EL_LT;} - "<=" { return SqlTypes.EL_LE;} - ">" { return SqlTypes.EL_GT;} - ">=" { return SqlTypes.EL_GE;} + "<" { return SqlTypes.LT;} + "<=" { return SqlTypes.LE;} + ">" { return SqlTypes.GT;} + ">=" { return SqlTypes.GE;} "!" { return SqlTypes.EL_NOT;} "&&" { return SqlTypes.EL_AND;} "||" { return SqlTypes.EL_OR;} "new" { return SqlTypes.EL_NEW;} "null" { return SqlTypes.EL_NULL;} - "true" { return SqlTypes.EL_BOOLEAN;} - "false" { return SqlTypes.EL_BOOLEAN;} + "true" { return SqlTypes.BOOLEAN;} + "false" { return SqlTypes.BOOLEAN;} {El_Number} { return SqlTypes.EL_NUMBER; } {El_String} { return SqlTypes.EL_STRING; } {El_Char} { return SqlTypes.EL_CHAR; } @@ -162,8 +230,8 @@ El_NonWordPart = [=<>\-,/*();\R \n\t\f] "%populate"/{El_NonWordPart} { yybegin(EXPRESSION); return SqlTypes.EL_POPULATE; } "%end"/{El_NonWordPart} { yybegin(EXPRESSION); return SqlTypes.EL_END; } "%!" { yybegin(PARSER_LEVEL_COMMENT); return SqlTypes.EL_PARSER_LEVEL_COMMENT; } - "#" { yybegin(EXPRESSION); return SqlTypes.EL_HASH; } - "^" { yybegin(EXPRESSION); return SqlTypes.EL_CARET; } + "#" { yybegin(EXPRESSION); return SqlTypes.HASH; } + "^" { yybegin(EXPRESSION); return SqlTypes.CARET; } ({LineTerminator}|{WhiteSpace})+ { return TokenType.WHITE_SPACE; } [^] { return TokenType.BAD_CHARACTER; } } diff --git a/src/main/java/org/domaframework/doma/intellij/highlighter/SqlColorSettingsPage.java b/src/main/java/org/domaframework/doma/intellij/highlighter/SqlColorSettingsPage.java index 6cb57ed3..3eb1fbba 100644 --- a/src/main/java/org/domaframework/doma/intellij/highlighter/SqlColorSettingsPage.java +++ b/src/main/java/org/domaframework/doma/intellij/highlighter/SqlColorSettingsPage.java @@ -37,6 +37,7 @@ public class SqlColorSettingsPage implements ColorSettingsPage { new AttributesDescriptor("SQL//Literal//String", SqlSyntaxHighlighter.STRING), new AttributesDescriptor("SQL//Literal//Number", SqlSyntaxHighlighter.NUMBER), new AttributesDescriptor("SQL//Syntax//Keyword", SqlSyntaxHighlighter.KEYWORD), + new AttributesDescriptor("SQL//Syntax//DataType", SqlSyntaxHighlighter.DATATYPE), new AttributesDescriptor("SQL//Syntax//Other", SqlSyntaxHighlighter.OTHER), new AttributesDescriptor("SQL//Syntax//Word", SqlSyntaxHighlighter.WORD), new AttributesDescriptor("SQL//Comment//Line comment", SqlSyntaxHighlighter.LINE_COMMENT), @@ -76,9 +77,9 @@ public class SqlColorSettingsPage implements ColorSettingsPage { new AttributesDescriptor("Expression//Operator///", SqlSyntaxHighlighter.EL_SLASH), new AttributesDescriptor("Expression//Operator//==", SqlSyntaxHighlighter.EL_EQ), new AttributesDescriptor("Expression//Operator//!=", SqlSyntaxHighlighter.EL_NE), - new AttributesDescriptor("Expression//Operator//>=", SqlSyntaxHighlighter.EL_GE), - new AttributesDescriptor("Expression//Operator//<=", SqlSyntaxHighlighter.EL_LE), - new AttributesDescriptor("Expression//Operator//>", SqlSyntaxHighlighter.EL_GT), + new AttributesDescriptor("Expression//Operator//>=", SqlSyntaxHighlighter.GE), + new AttributesDescriptor("Expression//Operator//<=", SqlSyntaxHighlighter.LE), + new AttributesDescriptor("Expression//Operator//>", SqlSyntaxHighlighter.GT), new AttributesDescriptor("Expression//Operator//&&", SqlSyntaxHighlighter.EL_AND), new AttributesDescriptor("Expression//Operator//||", SqlSyntaxHighlighter.EL_OR), // Comment diff --git a/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java b/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java index 1f4251d9..0a57b961 100644 --- a/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java +++ b/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java @@ -36,6 +36,8 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { createTextAttributesKey("DOMA_SQL_KEYWORD", DefaultLanguageHighlighterColors.METADATA); public static final TextAttributesKey WORD = createTextAttributesKey("DOMA_SQL_WORD", DefaultLanguageHighlighterColors.IDENTIFIER); + public static final TextAttributesKey DATATYPE = + createTextAttributesKey("DOMA_SQL_DATATYPE", DefaultLanguageHighlighterColors.METADATA); public static final TextAttributesKey STRING = createTextAttributesKey("DOMA_SQL_STRING", DefaultLanguageHighlighterColors.STRING); public static final TextAttributesKey NUMBER = @@ -107,14 +109,14 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { createTextAttributesKey("EL_EQ", DefaultLanguageHighlighterColors.OPERATION_SIGN); public static final TextAttributesKey EL_NE = createTextAttributesKey("EL_NE", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey EL_LT = - createTextAttributesKey("EL_LT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey EL_LE = - createTextAttributesKey("EL_LE", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey EL_GT = - createTextAttributesKey("EL_GT", DefaultLanguageHighlighterColors.OPERATION_SIGN); - public static final TextAttributesKey EL_GE = - createTextAttributesKey("EL_GE", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey LT = + createTextAttributesKey("LT", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey LE = + createTextAttributesKey("LE", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey GT = + createTextAttributesKey("GT", DefaultLanguageHighlighterColors.OPERATION_SIGN); + public static final TextAttributesKey GE = + createTextAttributesKey("GE", DefaultLanguageHighlighterColors.OPERATION_SIGN); public static final TextAttributesKey EL_NOT = createTextAttributesKey("EL_NOT", DefaultLanguageHighlighterColors.OPERATION_SIGN); public static final TextAttributesKey EL_AND = @@ -138,6 +140,7 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { static { map.put(SqlTypes.KEYWORD, KEYWORD); + map.put(SqlTypes.DATATYPE, DATATYPE); map.put(SqlTypes.STRING, STRING); map.put(SqlTypes.OTHER, OTHER); map.put(SqlTypes.WORD, WORD); @@ -156,36 +159,36 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { map.put(SqlTypes.EL_PARSER_LEVEL_COMMENT, EL_PARSER_LEVEL_COMMENT); map.put(SqlTypes.EL_FOR, EL_FOR); map.put(SqlTypes.EL_END, EL_END); - map.put(SqlTypes.EL_HASH, EL_HASH); - map.put(SqlTypes.EL_CARET, EL_CARET); + map.put(SqlTypes.HASH, EL_HASH); + map.put(SqlTypes.CARET, EL_CARET); - map.put(SqlTypes.EL_DOT, EL_DOT); - map.put(SqlTypes.EL_COMMA, EL_COMMA); - map.put(SqlTypes.EL_AT_SIGN, EL_AT_SIGN); - map.put(SqlTypes.EL_LEFT_PAREN, EL_LEFT_PAREN); - map.put(SqlTypes.EL_RIGHT_PAREN, EL_RIGHT_PAREN); + map.put(SqlTypes.DOT, EL_DOT); + map.put(SqlTypes.COMMA, EL_COMMA); + map.put(SqlTypes.AT_SIGN, EL_AT_SIGN); + map.put(SqlTypes.LEFT_PAREN, EL_LEFT_PAREN); + map.put(SqlTypes.RIGHT_PAREN, EL_RIGHT_PAREN); map.put(SqlTypes.EL_PLUS, EL_PLUS); map.put(SqlTypes.EL_MINUS, EL_MINUS); - map.put(SqlTypes.EL_ASTERISK, EL_ASTERISK); - map.put(SqlTypes.EL_SLASH, EL_SLASH); + map.put(SqlTypes.ASTERISK, EL_ASTERISK); + map.put(SqlTypes.SLASH, EL_SLASH); map.put(SqlTypes.EL_PERCENT, EL_PERCENT); map.put(SqlTypes.EL_EQ, EL_EQ); map.put(SqlTypes.EL_NE, EL_NE); - map.put(SqlTypes.EL_LT, EL_LT); - map.put(SqlTypes.EL_LE, EL_LE); - map.put(SqlTypes.EL_GT, EL_GT); - map.put(SqlTypes.EL_GE, EL_GE); + map.put(SqlTypes.LT, LT); + map.put(SqlTypes.LE, LE); + map.put(SqlTypes.GT, GT); + map.put(SqlTypes.GE, GE); map.put(SqlTypes.EL_NOT, EL_NOT); map.put(SqlTypes.EL_AND, EL_AND); map.put(SqlTypes.EL_OR, EL_OR); - map.put(SqlTypes.EL_SEPARATOR, EL_SEPARATOR); + map.put(SqlTypes.SEPARATOR, EL_SEPARATOR); map.put(SqlTypes.EL_NEW, EL_NEW); map.put(SqlTypes.EL_NULL, EL_NULL); - map.put(SqlTypes.EL_BOOLEAN, EL_BOOLEAN); + map.put(SqlTypes.BOOLEAN, EL_BOOLEAN); map.put(SqlTypes.EL_STRING, EL_STRING); map.put(SqlTypes.EL_CHAR, EL_CHAR); map.put(SqlTypes.EL_NUMBER, EL_NUMBER); diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt index 607359db..17d3f2f8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/FileTypeCheck.kt @@ -37,7 +37,6 @@ fun getExtension(type: String): String = * Does it match the Dao file type condition? */ fun isJavaOrKotlinFileType(daoFile: PsiFile): Boolean { - if (daoFile.virtualFile == null) return false val fileType = FileTypeManager.getInstance().getFileTypeByFile(daoFile.virtualFile) return when (fileType.name) { "JAVA", "Kotlin" -> true diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/BindDirectiveUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/BindDirectiveUtil.kt index f0c9eec0..9c10ee77 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/BindDirectiveUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/BindDirectiveUtil.kt @@ -42,7 +42,7 @@ object BindDirectiveUtil { p.text == "%" || p.prevSibling?.text == "%" || p is SqlElStaticFieldAccessExpr || - p.elementType == SqlTypes.EL_AT_SIGN + p.elementType == SqlTypes.AT_SIGN ) || p.elementType == SqlTypes.BLOCK_COMMENT_START || !p.isNotWhiteSpace() @@ -51,7 +51,7 @@ object BindDirectiveUtil { !it.isNotWhiteSpace() -> DirectiveType.NOT_DIRECTIVE it.text == "%" || it.prevSibling?.text == "%" -> DirectiveType.PERCENT it is SqlElStaticFieldAccessExpr -> DirectiveType.STATIC - it.elementType == SqlTypes.EL_AT_SIGN -> DirectiveType.BUILT_IN + it.elementType == SqlTypes.AT_SIGN -> DirectiveType.BUILT_IN else -> DirectiveType.NOT_DIRECTIVE } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveHandler.kt index 11db41d8..2ae0ea6b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveHandler.kt @@ -33,8 +33,7 @@ open class DirectiveHandler( ): Boolean { val prev = it.prevLeaf() return ( - prev != null && - prev.text == symbol || + prev?.text == symbol || it.text.startsWith(symbol) ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt index 82515d10..eccfa461 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt @@ -80,7 +80,7 @@ class StaticDirectiveHandler( CompletionSuggest(fields ?: emptyList(), methods ?: emptyList()) } } - } else if (element.prevSibling?.elementType == SqlTypes.EL_AT_SIGN) { + } else if (element.prevSibling?.elementType == SqlTypes.AT_SIGN) { // Built-in function completion handleResult = builtInDirectiveHandler(element, result) { bind -> diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index d070d526..a123eca9 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -108,7 +108,7 @@ class SqlParameterCompletionProvider : CompletionProvider( val prevElm = pos.prevLeafs.firstOrNull { it.isNotWhiteSpace() && - it.elementType != SqlTypes.EL_DOT && + it.elementType != SqlTypes.DOT && it !is PsiErrorElement } if (!pos.isNotWhiteSpace() && !isRightFactor(prevElm)) return @@ -143,8 +143,8 @@ class SqlParameterCompletionProvider : CompletionProvider( prevElm is SqlElAndExpr || prevElm?.elementType == SqlTypes.EL_PLUS || prevElm?.elementType == SqlTypes.EL_MINUS || - prevElm?.elementType == SqlTypes.EL_ASTERISK || - prevElm?.elementType == SqlTypes.EL_SLASH || + prevElm?.elementType == SqlTypes.ASTERISK || + prevElm?.elementType == SqlTypes.SLASH || prevElm?.elementType == SqlTypes.EL_PERCENT || prevElm?.isNotWhiteSpace() == true ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiElementExtension.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiElementExtension.kt index 6b256c56..0a112f58 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiElementExtension.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiElementExtension.kt @@ -31,7 +31,7 @@ fun PsiElement.findSelfBlocks(): List { var elms = emptyList() for (it in this.prevLeafs) { elms = elms.plus(it) - if (!it.isNotWhiteSpace() || it.elementType == SqlTypes.EL_AT_SIGN) break + if (!it.isNotWhiteSpace() || it.elementType == SqlTypes.AT_SIGN) break } elms diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlBlockBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlBlockBuilder.kt new file mode 100644 index 00000000..64cc68df --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlBlockBuilder.kt @@ -0,0 +1,78 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubGroupBlock + +open class SqlBlockBuilder { + private val groupTopNodeIndexHistory = mutableListOf>() + + private val commentBlocks = mutableListOf() + + fun getGroupTopNodeIndexHistory(): List> = groupTopNodeIndexHistory + + fun addGroupTopNodeIndexHistory(block: Pair) { + groupTopNodeIndexHistory.add(block) + } + + fun addCommentBlock(block: SqlBlock) { + commentBlocks.add(block) + } + + fun updateCommentBlockIndent(baseIndent: SqlBlock) { + if (commentBlocks.isNotEmpty()) { + var index = 0 + commentBlocks.forEach { block -> + val indentLen = + if (index == 0 && + baseIndent.parentBlock is SqlSubGroupBlock && + baseIndent.parentBlock?.childBlocks?.size == 1 + ) { + 1 + } else { + baseIndent.indent.indentLen + } + block.indent.indentLevel = IndentType.NONE + block.indent.indentLen = indentLen + block.indent.groupIndentLen = 0 + index++ + } + commentBlocks.clear() + } + } + + fun getLastGroupTopNodeIndexHistory(): Pair? = groupTopNodeIndexHistory.lastOrNull() + + fun removeLastGroupTopNodeIndexHistory() { + if (groupTopNodeIndexHistory.isNotEmpty()) { + groupTopNodeIndexHistory.removeLast() + } + } + + fun clearSubListGroupTopNodeIndexHistory(start: Int) { + groupTopNodeIndexHistory + .subList( + start, + groupTopNodeIndexHistory.size, + ).clear() + } + + fun getGroupTopNodeIndexByIndentType(indentType: IndentType): Int = + groupTopNodeIndexHistory.indexOfLast { + it.second.indent.indentLevel == indentType + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlCustomSpacingBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlCustomSpacingBuilder.kt new file mode 100644 index 00000000..a6b2d593 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlCustomSpacingBuilder.kt @@ -0,0 +1,119 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +import com.intellij.formatting.ASTBlock +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.psi.tree.IElementType +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlColumnBlock +import org.domaframework.doma.intellij.formatter.block.SqlRightPatternBlock +import org.domaframework.doma.intellij.formatter.block.SqlWhitespaceBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionRawGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock + +class SqlCustomSpacingBuilder { + companion object { + val normalSpacing: Spacing = Spacing.createSpacing(1, 1, 0, false, 0, 0) + val nonSpacing: Spacing = Spacing.createSpacing(0, 0, 0, false, 0, 0) + } + + private val spacingRules: MutableMap?, Spacing?> = HashMap() + + fun withSpacing( + left: IElementType?, + right: IElementType?, + spacing: Spacing?, + ): SqlCustomSpacingBuilder { + spacingRules.put(Pair(left, right), spacing) + return this + } + + fun getCustomSpacing( + child1: Block?, + child2: Block?, + ): Spacing? { + if (child1 is ASTBlock && child2 is ASTBlock) { + val type1: IElementType? = child1.node?.elementType + val type2: IElementType? = child2.node?.elementType + val spacing: Spacing? = spacingRules[Pair(type1, type2)] + if (spacing != null) { + return spacing + } + } + if (child1 == null && child2 is ASTBlock) { + val type2: IElementType? = child2.node?.elementType + val spacing: Spacing? = spacingRules[Pair(null, type2)] + if (spacing != null) { + return spacing + } + } + return null + } + + fun getSpacingWithIndentComma( + child1: SqlBlock?, + child2: SqlBlock, + ): Spacing? { + val indentLen: Int = child2.indent.indentLen + when (child1) { + null -> return Spacing.createSpacing(0, 0, 0, false, 0, 0) + is SqlWhitespaceBlock -> { + val afterNewLine = child1.node.text.substringAfterLast("\n", "") + if (child1.node.text.contains("\n")) { + val currentIndent = afterNewLine.length + val newIndent = + if (currentIndent != indentLen) { + indentLen + } else { + 0 + } + return Spacing.createSpacing(newIndent, newIndent, 0, false, 0, 0) + } + } + else -> { + return Spacing.createSpacing(indentLen, indentLen, 1, false, 0, 1) + } + } + return null + } + + fun getSpacing(child2: SqlNewGroupBlock): Spacing? = + Spacing.createSpacing( + child2.indent.indentLen, + child2.indent.indentLen, + 0, + false, + 0, + 0, + ) + + fun getSpacingColumnDefinition(child: SqlColumnBlock): Spacing? { + val indentLen = child.createBlockIndentLen() + return Spacing.createSpacing(indentLen, indentLen, 0, false, 0, 0) + } + + fun getSpacingColumnDefinitionRaw(child: SqlColumnDefinitionRawGroupBlock): Spacing? { + val indentLen = child.createBlockIndentLen() + return Spacing.createSpacing(indentLen, indentLen, 0, false, 0, 0) + } + + fun getSpacingColumnDefinitionRawEndRight(child: SqlRightPatternBlock): Spacing? { + val indentLen = child.indent.indentLen + return Spacing.createSpacing(indentLen, indentLen, 0, false, 0, 0) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt new file mode 100644 index 00000000..409e4b16 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatPreProcessor.kt @@ -0,0 +1,350 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +import com.intellij.lang.ASTNode +import com.intellij.openapi.editor.Document +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiRecursiveElementVisitor +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.TokenType +import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.elementType +import com.intellij.psi.util.endOffset +import com.intellij.psi.util.prevLeafs +import org.domaframework.doma.intellij.psi.SqlBlockComment +import org.domaframework.doma.intellij.psi.SqlTypes +import org.domaframework.doma.intellij.setting.SqlLanguage +import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class SqlFormatPreProcessor : PreFormatProcessor { + override fun process( + node: ASTNode, + rangeToReformat: TextRange, + ): TextRange = processText(node.psi.containingFile, rangeToReformat) + + private fun processText( + source: PsiFile, + rangeToReformat: TextRange, + ): TextRange { + if (!isEnableFormat()) return rangeToReformat + if (source.language != SqlLanguage.INSTANCE) return rangeToReformat + + val visitor = SqlFormatVisitor() + source.accept(visitor) + + val docManager = PsiDocumentManager.getInstance(source.project) + val document = docManager.getDocument(source) ?: return rangeToReformat + + val keywordList = visitor.replaces.filter { it.elementType != TokenType.WHITE_SPACE } + val replaceKeywordList = visitor.replaces.filter { it.elementType == SqlTypes.KEYWORD } + var index = keywordList.size + var keywordIndex = replaceKeywordList.size + + visitor.replaces.asReversed().forEach { + val createQueryType = getCreateQueryGroup(keywordList, index) + val textRangeStart = it.startOffset + val textRangeEnd = textRangeStart + it.text.length + if (it.elementType != TokenType.WHITE_SPACE) { + // Add a newline before any element that needs a newline+indent, without overlapping if there is already a newline + index-- + var newKeyword = getUpperText(it) + when (it.elementType) { + SqlTypes.KEYWORD -> { + keywordIndex-- + newKeyword = getKeywordNewText(index, it, createQueryType, keywordList) + } + + SqlTypes.LEFT_PAREN -> { + newKeyword = + if (createQueryType == CreateQueryType.TABLE) { + getNewLineString(it.prevSibling, getUpperText(it)) + } else if (keywordIndex > 0) { + if (replaceKeywordList[keywordIndex - 1].text.lowercase() == "insert" || + replaceKeywordList[keywordIndex - 1].text.lowercase() == "into" + ) { + getNewLineString(it.prevSibling, getUpperText(it)) + } else { + getUpperText(it) + } + } else { + getUpperText(it) + } + } + + SqlTypes.RIGHT_PAREN -> { + newKeyword = getRightPatternNewText(it, newKeyword, createQueryType) + } + + SqlTypes.WORD -> { + newKeyword = getWordNewText(it, newKeyword, createQueryType) + } + + SqlTypes.COMMA -> { + newKeyword = getNewLineString(it.prevSibling, getUpperText(it)) + } + } + document.deleteString(textRangeStart, textRangeEnd) + document.insertString(textRangeStart, newKeyword) + } else { + // Remove spaces after newlines to reset indentation + val nextSibling = it.nextSibling + if (nextSibling.elementType == SqlTypes.BLOCK_COMMENT) { + removeSpacesAroundNewline(document, it.textRange) + } else if (keywordIndex < replaceKeywordList.size) { + val nextElement = replaceKeywordList[keywordIndex] + if (isNewLineOnlyCreateTable(nextSibling) && createQueryType == CreateQueryType.TABLE) { + removeSpacesAroundNewline(document, it.textRange) + } else if (isSubGroupFirstElement(nextElement)) { + document.deleteString(textRangeStart, textRangeEnd) + } else if (isCreateViewAs(replaceKeywordList[keywordIndex], createQueryType)) { + removeSpacesAroundNewline(document, it.textRange) + } else { + val isNewLineGroup = SqlKeywordUtil.getIndentType(nextElement.text ?: "").isNewLineGroup() + val isSetLineKeyword = + if (keywordIndex > 0) { + SqlKeywordUtil.isSetLineKeyword( + nextElement.text, + replaceKeywordList[keywordIndex - 1].text, + ) + } else { + false + } + + if (isNewLineGroup && !isSetLineKeyword || keywordList[index].elementType == SqlTypes.COMMA) { + removeSpacesAroundNewline(document, it.textRange) + } else { + document.replaceString(textRangeStart, textRangeEnd, " ") + } + } + } else { + removeSpacesAroundNewline(document, it.textRange) + } + } + } + + docManager.commitDocument(document) + + return rangeToReformat.grown(visitor.replaces.size) + } + + private fun isEnableFormat(): Boolean { + val setting = DomaToolsFunctionEnableSettings.getInstance() + val isEnableFormat = setting.state.isEnableSqlFormat + return isEnableFormat + } + + private fun removeSpacesAroundNewline( + document: Document, + range: TextRange, + ) { + val originalText = document.getText(range) + val newText = originalText.replace(Regex("\\s*\\n\\s*"), "\n") + document.replaceString(range.startOffset, range.endOffset, newText) + } + + /** + * Checks for special case keyword elements and specific combinations of keywords with line breaks and capitalization only + */ + private fun getKeywordNewText( + index: Int, + element: PsiElement, + createQueryType: CreateQueryType, + keywordList: List, + ): String = + if (element.text.lowercase() == "end") { + getNewLineString(element.prevSibling, getUpperText(element)) + } else if (isCreateViewAs(element, createQueryType)) { + getNewLineString(element.prevSibling, getUpperText(element)) + } else if (isSubGroupFirstElement(element)) { + getUpperText(element) + } else if (SqlKeywordUtil.getIndentType(element.text).isNewLineGroup()) { + if (index > 0 && + SqlKeywordUtil.isSetLineKeyword( + element.text, + keywordList[index - 1].text, + ) + ) { + getUpperText(element) + } else { + getNewLineString(element.prevSibling, getUpperText(element)) + } + } else { + getUpperText(element) + } + + private fun getRightPatternNewText( + element: PsiElement, + newKeyword: String, + createQueryType: CreateQueryType, + ): String { + var newKeyword1 = newKeyword + val prefixElements = + getElementsBeforeKeyword(element.prevLeafs.toList()) { it.elementType == SqlTypes.LEFT_PAREN } + val containsColumnRaw = + prefixElements.findLast { isColumnDefinedRawElementType(it) } != null + newKeyword1 = + if (createQueryType == CreateQueryType.TABLE) { + if (containsColumnRaw) { + getNewLineString(element.prevSibling, getUpperText(element)) + } else { + getUpperText(element) + } + } else { + getUpperText(element) + } + return newKeyword1 + } + + private fun getWordNewText( + element: PsiElement, + newKeyword: String, + createQueryType: CreateQueryType, + ): String { + newKeyword + var prev = element.prevSibling + var isColumnName = true + while (prev != null && prev.elementType != SqlTypes.LEFT_PAREN && prev.elementType != SqlTypes.COMMA) { + if (prev !is PsiWhiteSpace && + prev.elementType != SqlTypes.LINE_COMMENT && + prev.elementType != SqlTypes.BLOCK_COMMENT + ) { + isColumnName = + prev.elementType == SqlTypes.COMMA || + prev.elementType == SqlTypes.LEFT_PAREN + break + } + prev = prev.prevSibling + } + + return if (createQueryType == CreateQueryType.TABLE && isColumnName) { + getNewLineString(element.prevSibling, getUpperText(element)) + } else { + getUpperText(element) + } + } + + private fun isCreateViewAs( + element: PsiElement, + createQueryType: CreateQueryType, + ): Boolean = + element.text.lowercase() == "as" && + createQueryType == CreateQueryType.VIEW + + private fun isColumnDefinedRawElementType(element: PsiElement): Boolean = + element.elementType == SqlTypes.WORD || + element.elementType == SqlTypes.KEYWORD || + element.elementType == SqlTypes.COMMA + + private fun getCreateQueryGroup( + keywordList: List, + index: Int, + ): CreateQueryType { + var topLastKeyWord: PsiElement? = null + var attachmentKeywordType = CreateQueryType.NONE + keywordList + .dropLast(keywordList.size.minus(index)) + .filter { + it.elementType == SqlTypes.KEYWORD + }.asReversed() + .forEach { key -> + if (SqlKeywordUtil.isTopKeyword(key.text)) { + topLastKeyWord = key + return@forEach + } + if (SqlKeywordUtil.isAttachedKeyword(key.text)) { + attachmentKeywordType = CreateQueryType.getCreateTableType(key.text) + } + } + val prevKeywordText = topLastKeyWord?.text?.lowercase() + val isCreateGroup = prevKeywordText == "create" + if (!isCreateGroup) return CreateQueryType.NONE + return attachmentKeywordType + } + + /** + * The column definition elements of Create Table, "(", "WORD", and ")" must be line breaks + */ + private fun isNewLineOnlyCreateTable(nextElement: PsiElement): Boolean = + nextElement.elementType == SqlTypes.LEFT_PAREN || + nextElement.elementType == SqlTypes.RIGHT_PAREN || + nextElement.elementType == SqlTypes.WORD + + fun getElementsBeforeKeyword( + elements: List, + isLeft: (T) -> Boolean, + ): List = elements.takeWhile { element -> !isLeft(element) } + + private fun getNewLineString( + prevElement: PsiElement?, + text: String, + ): String = + if (prevElement?.text?.contains("\n") == false) { + "\n$text" + } else { + text + } + + private fun getUpperText(element: PsiElement): String = + if (element.elementType == SqlTypes.KEYWORD) { + element.text.uppercase() + } else { + element.text + } + + private fun isSubGroupFirstElement(element: PsiElement): Boolean = + getElementsBeforeKeyword(element.prevLeafs.toList()) { it.elementType == SqlTypes.LEFT_PAREN } + .findLast { it !is PsiWhiteSpace } == null +} + +private class SqlFormatVisitor : PsiRecursiveElementVisitor() { + val replaces = mutableListOf() + var lastElement: PsiElement? = null + + override fun visitElement(element: PsiElement) { + super.visitElement(element) + if (element !is PsiFile && element.nextSibling == null) { + lastElement = element + } + + if (PsiTreeUtil.getParentOfType(element, SqlBlockComment::class.java) == null) { + when (element.elementType) { + SqlTypes.KEYWORD, SqlTypes.COMMA, SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN, SqlTypes.WORD -> { + replaces.add(element) + } + } + } + } + + override fun visitWhiteSpace(space: PsiWhiteSpace) { + super.visitWhiteSpace(space) + val nextElement = space.nextSibling + if (nextElement != null && + ( + space.text.contains("\n") || + nextElement.elementType == SqlTypes.LINE_COMMENT || + nextElement.elementType == SqlTypes.BLOCK_COMMENT + ) + ) { + replaces.add(space) + } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt new file mode 100644 index 00000000..2a1eda37 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlFormattingModelBuilder.kt @@ -0,0 +1,132 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +import com.intellij.formatting.Alignment +import com.intellij.formatting.FormattingContext +import com.intellij.formatting.FormattingModel +import com.intellij.formatting.FormattingModelBuilder +import com.intellij.formatting.FormattingModelProvider +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.formatting.WrapType +import com.intellij.psi.TokenType +import com.intellij.psi.codeStyle.CodeStyleSettings +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes +import org.domaframework.doma.intellij.setting.SqlLanguage +import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings + +class SqlFormattingModelBuilder : FormattingModelBuilder { + override fun createModel(formattingContext: FormattingContext): FormattingModel { + val codeStyleSettings = formattingContext.codeStyleSettings + val setting = DomaToolsFunctionEnableSettings.getInstance() + val isEnableFormat = setting.state.isEnableSqlFormat + + val spacingBuilder = + if (!isEnableFormat) { + SpacingBuilder(codeStyleSettings, SqlLanguage.INSTANCE) + } else { + createSpaceBuilder(codeStyleSettings) + } + val customSpacingBuilder = + if (!isEnableFormat) null else createCustomSpacingBuilder() + + return FormattingModelProvider + .createFormattingModelForPsiFile( + formattingContext.containingFile, + SqlBlock( + formattingContext.node, + Wrap.createWrap(WrapType.NONE, false), + Alignment.createAlignment(), + customSpacingBuilder, + spacingBuilder, + isEnableFormat, + ), + codeStyleSettings, + ) + } + + private fun createSpaceBuilder(settings: CodeStyleSettings): SpacingBuilder = + SpacingBuilder(settings, SqlLanguage.INSTANCE) + .around(SqlTypes.DOT) + .spacing(0, 0, 0, false, 0) + .after(SqlTypes.COMMA) + .spacing(1, 1, 0, false, 0) + .before(SqlTypes.LINE_COMMENT) + .spacing(1, 1, 0, false, 0) + .before(SqlTypes.BLOCK_COMMENT) + .spacing(0, 0, 1, false, 0) + .before(SqlTypes.KEYWORD) + .spacing(1, 1, 0, false, 0) + .before(SqlTypes.LEFT_PAREN) + .spacing(1, 1, 0, false, 0) + .after(SqlTypes.LEFT_PAREN) + .spacing(0, 0, 0, false, 0) + .around(SqlTypes.WORD) + .spacing(1, 1, 0, false, 0) + .before(SqlTypes.RIGHT_PAREN) + .spacing(0, 0, 0, false, 0) + .around(SqlTypes.PLUS) + .spacing(1, 1, 0, false, 0) + .around(SqlTypes.MINUS) + .spacing(1, 1, 0, false, 0) + .around(SqlTypes.ASTERISK) + .spacing(1, 1, 0, false, 0) + + private fun createCustomSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + TokenType.WHITE_SPACE, + SqlTypes.KEYWORD, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.WORD, + TokenType.WHITE_SPACE, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.WORD, + SqlTypes.LEFT_PAREN, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.LEFT_PAREN, + SqlTypes.WORD, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.WORD, + SqlTypes.RIGHT_PAREN, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.OTHER, + TokenType.WHITE_SPACE, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.ASTERISK, + TokenType.WHITE_SPACE, + SqlCustomSpacingBuilder.nonSpacing, + ) + // Table And Column Rules + // WORD And OTHER Rules + .withSpacing( + SqlTypes.WORD, + SqlTypes.WORD, + SqlCustomSpacingBuilder.normalSpacing, + ).withSpacing( + SqlTypes.OTHER, + SqlTypes.OTHER, + SqlCustomSpacingBuilder.normalSpacing, + ) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlKeywordUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlKeywordUtil.kt new file mode 100644 index 00000000..fadc8bcb --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlKeywordUtil.kt @@ -0,0 +1,289 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +enum class IndentType( + private val level: Int, + private val group: Boolean = false, +) { + FILE(0, true), + TOP(1, true), + SECOND(2, true), + JOIN(3, true), + SECOND_OPTION(4, true), + TIRD(5), + ATTACHED(6), + INLINE_SECOND(8, true), + COLUMN(9, true), + SUB(90), + ATTRIBUTE(91), + LITERAL(92), + OPTIONS(93), + COMMA(94), + PARAM(95), + INLINE(96), + NONE(99), + ; + + fun isNewLineGroup(): Boolean = this.group || this.level == COMMA.level +} + +enum class CreateQueryType { + TABLE, + INDEX, + VIEW, + DATABASE, + NONE, + ; + + companion object { + fun getCreateTableType(text: String): CreateQueryType = + when (text.lowercase()) { + "table" -> TABLE + "index" -> INDEX + "view" -> VIEW + "database" -> DATABASE + else -> NONE + } + } +} + +class SqlKeywordUtil { + companion object { + private val TOP_KEYWORDS: Set = + setOf( + "select", + "update", + "insert", + "delete", + "drop", + "alter", + "create", + "truncate", + "rename", + "union", + ) + + fun isTopKeyword(keyword: String): Boolean = TOP_KEYWORDS.contains(keyword.lowercase()) + + private val SECOND_KEYWORD = + setOf( + "from", + "where", + "order", + "group", + "having", + "limit", + "values", + ) + + fun isSecondKeyword(keyword: String): Boolean = SECOND_KEYWORD.contains(keyword.lowercase()) + + private val SECOND_OPTION_KEYWORD = + setOf( + "and", + "or", + "on", + ) + + fun isSecondOptionKeyword(keyword: String): Boolean = SECOND_OPTION_KEYWORD.contains(keyword.lowercase()) + + private val BEFORE_TABLE_KEYWORD = + setOf( + "from", + "update", + "drop", + "table", + ) + + fun isBeforeTableKeyword(keyword: String): Boolean = BEFORE_TABLE_KEYWORD.contains(keyword.lowercase()) + + private val JOIN_KEYWORD = + setOf( + "left", + "right", + "full", + "cross", + "natural", + ) + + fun isJoinKeyword(keyword: String): Boolean = JOIN_KEYWORD.contains(keyword.lowercase()) + + private val JOIN_ATTACHED_KEYWORD = + setOf( + "outer", + "inner", + "join", + ) + + fun isJoinAttachedKeyword(keyword: String): Boolean = JOIN_ATTACHED_KEYWORD.contains(keyword.lowercase()) + + private val ATTACHED_KEYWORD = + setOf( + "distinct", + "into", + "table", + "index", + "database", + "view", + ) + + fun isAttachedKeyword(keyword: String): Boolean = ATTACHED_KEYWORD.contains(keyword.lowercase()) + + private val THIRD_KEYWORDS = + setOf( + "add", + "between", + "modify", + "column", + ) + + fun isThirdKeyword(keyword: String): Boolean = THIRD_KEYWORDS.contains(keyword.lowercase()) + + private val COLUMN_TYPE_KEYWORDS = + setOf( + "int", + "integer", + "smallint", + "bigint", + "tinyint", + "float", + "double", + "decimal", + "numeric", + "char", + "varchar", + "text", + "date", + "time", + "timestamp", + "datetime", + "boolean", + "bit", + "binary", + "varbinary", + "blob", + "clob", + "json", + "enum", + "set", + ) + + fun isColumnTypeKeyword(keyword: String): Boolean = COLUMN_TYPE_KEYWORDS.contains(keyword.lowercase()) + + private val LITERAL_KEYWORDS = + setOf( + "null", + "true", + "false", + "current_date", + ) + + fun isLiteralKeyword(keyword: String): Boolean = LITERAL_KEYWORDS.contains(keyword.lowercase()) + + private val ATTRIBUTE_KEYWORD = + setOf( + "default", + "key", + "unique", + "primary", + "foreign", + "constraint", + ) + + fun isAttributeKeyword(keyword: String): Boolean = ATTRIBUTE_KEYWORD.contains(keyword.lowercase()) + + private val INLINE_PARENT_SQL_KEYWORDS = + setOf( + "if", + "case", + ) + + fun isInlineParentSqlKeyword(keyword: String): Boolean = INLINE_PARENT_SQL_KEYWORDS.contains(keyword.lowercase()) + + private val INLINE_SQL_KEYWORDS = + setOf( + "when", + "else", + "end", + ) + + fun isInlineSqlKeyword(keyword: String): Boolean = INLINE_SQL_KEYWORDS.contains(keyword.lowercase()) + + private val OPTION_SQL_KEYWORDS = + setOf( + "as", + "by", + "to", + "asc", + "desc", + "all", + "check", + "exists", + "full", + "is", + "like", + "offset", + "then", + "in", + ) + + fun isOptionSqlKeyword(keyword: String): Boolean = OPTION_SQL_KEYWORDS.contains(keyword.lowercase()) + + private val SET_LINE_KEYWORDS = + mapOf( + "into" to setOf("insert"), + "from" to setOf("delete"), + "distinct" to setOf("select"), + "table" to setOf("create", "alter", "rename", "truncate", "drop"), + "index" to setOf("create", "alter", "rename", "truncate", "drop"), + "view" to setOf("create", "alter", "rename", "truncate", "drop"), + "database" to setOf("create", "alter", "rename", "truncate", "drop"), + "join" to setOf("outer", "inner", "left", "right"), + "outer" to setOf("left", "right"), + "inner" to setOf("left", "right"), + "by" to setOf("group", "order"), + "and" to setOf("between"), + "if" to setOf("table", "create"), + ) + + fun isSetLineKeyword( + keyword: String, + prevKeyword: String, + ): Boolean = SET_LINE_KEYWORDS[keyword.lowercase()]?.contains(prevKeyword.lowercase()) == true + + fun getIndentType(keywordText: String): IndentType { + val keyword = keywordText.lowercase() + return when { + isTopKeyword(keyword) -> IndentType.TOP + isSecondKeyword(keyword) -> IndentType.SECOND + isSecondOptionKeyword(keyword) -> IndentType.SECOND_OPTION + isJoinKeyword(keyword) -> IndentType.JOIN + isJoinAttachedKeyword(keyword) -> IndentType.JOIN + isAttachedKeyword(keyword) -> IndentType.ATTACHED + isThirdKeyword(keyword) -> IndentType.TIRD + isInlineParentSqlKeyword(keyword) -> IndentType.INLINE + isInlineSqlKeyword(keyword) -> IndentType.INLINE_SECOND + isAttributeKeyword(keyword) -> IndentType.ATTRIBUTE + isLiteralKeyword(keyword) -> IndentType.LITERAL + isOptionSqlKeyword(keyword) -> IndentType.OPTIONS + isColumnTypeKeyword(keyword) -> IndentType.COLUMN + keyword == "," -> IndentType.COMMA + else -> IndentType.NONE + } + } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt new file mode 100644 index 00000000..c1c5e91a --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/SqlPostProcessor.kt @@ -0,0 +1,58 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor + +class SqlPostProcessor : PostFormatProcessor { + override fun processElement( + source: PsiElement, + settings: CodeStyleSettings, + ): PsiElement = source + + override fun processText( + source: PsiFile, + rangeToReformat: TextRange, + settings: CodeStyleSettings, + ): TextRange { + val project: Project = source.project + val document = PsiDocumentManager.getInstance(project).getDocument(source) ?: return rangeToReformat + + val originalText = document.text + val withoutTrailingSpaces = originalText.replace(Regex(" +(\r?\n)"), "$1") + val finalText = withoutTrailingSpaces.trimEnd() + " \n" + + if (originalText == finalText) { + return rangeToReformat + } + + ApplicationManager.getApplication().invokeAndWait { + WriteCommandAction.runWriteCommandAction(project) { + document.setText(finalText) + PsiDocumentManager.getInstance(project).commitDocument(document) + } + } + return TextRange(0, finalText.length) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt new file mode 100644 index 00000000..f5944559 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -0,0 +1,805 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.ChildAttributes +import com.intellij.formatting.Indent +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.util.PsiTreeUtil +import org.domaframework.doma.intellij.formatter.CreateQueryType +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.SqlBlockBuilder +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.SqlKeywordUtil +import org.domaframework.doma.intellij.formatter.block.expr.SqlElBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElDotBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionRawGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlCreateKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlDataTypeParamBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInlineGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInlineSecondGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInsertColumnGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInsertKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlJoinGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlViewGroupBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +open class SqlBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val customSpacingBuilder: SqlCustomSpacingBuilder?, + internal val spacingBuilder: SpacingBuilder, + private val enableFormat: Boolean = false, +) : AbstractBlock( + node, + wrap, + alignment, + ) { + data class ElementIndent( + var indentLevel: IndentType, + var indentLen: Int, + var groupIndentLen: Int, + ) + + val blocks = mutableListOf() + open var parentBlock: SqlBlock? = null + open val childBlocks = mutableListOf() + open val indent: ElementIndent = + ElementIndent( + IndentType.FILE, + 0, + 0, + ) + + private val blockBuilder = SqlBlockBuilder() + + protected open val pendingCommentBlocks = mutableListOf() + + protected fun isEnableFormat(): Boolean = enableFormat + + open fun setParentGroupBlock(block: SqlBlock?) { + parentBlock = block + parentBlock?.addChildBlock(this) + } + + open fun addChildBlock(childBlock: SqlBlock) { + childBlocks.add(childBlock) + } + + public override fun buildChildren(): MutableList { + if (isLeaf || !isEnableFormat()) return mutableListOf() + + var child = node.firstChildNode + var prevNonWhiteSpaceNode: ASTNode? = null + blockBuilder.addGroupTopNodeIndexHistory(Pair(0, this)) + while (child != null) { + val lastBlock = blocks.lastOrNull() + val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory()?.second + if (child !is PsiWhiteSpace) { + val childBlock = getBlock(child) + if (blocks.isNotEmpty() && lastBlock is SqlWhitespaceBlock) { + if (isSaveWhiteSpace(childBlock, child, lastGroup)) { + val whiteBlock = lastBlock as SqlBlock + whiteBlock.parentBlock = lastGroup + } else { + // Ignore space blocks for non-breaking elements + blocks.removeLast() + } + } + prevNonWhiteSpaceNode = child + updateSearchKeywordLevelHistory(childBlock, child) + setRightSpace(childBlock) + blocks.add(childBlock) + if (childBlock is SqlCommentBlock) { + blockBuilder.addCommentBlock(childBlock) + } + } else { + if (lastBlock !is SqlLineCommentBlock) { + blocks.add( + SqlWhitespaceBlock( + child, + parentBlock, + wrap, + alignment, + spacingBuilder, + ), + ) + } + } + child = child.treeNext + } + blocks.addAll(pendingCommentBlocks) + + return blocks + } + + private fun isSaveWhiteSpace( + childBlock: SqlBlock, + child: ASTNode, + lastGroup: SqlBlock?, + ): Boolean = + isNewLineGroupBlock(childBlock, child, lastGroup) || + childBlock.node.elementType == SqlTypes.COMMA || + childBlock is SqlInsertColumnGroupBlock || + childBlock is SqlColumnDefinitionRawGroupBlock || + childBlock is SqlColumnDefinitionGroupBlock || + (childBlock is SqlRightPatternBlock && childBlock.isNeedBeforeWhiteSpace(lastGroup)) || + ( + ( + childBlock is SqlLineCommentBlock || + childBlock is SqlBlockCommentBlock + ) && + child.treePrev.text.contains("\n") + ) || + (childBlock is SqlElBlockCommentBlock && childBlock.isConditionLoopBlock) + + private fun setRightSpace(currentBlock: SqlBlock?) { + val rightBlock = currentBlock as? SqlRightPatternBlock + rightBlock?.enableLastRight() + } + + private fun isNewGroup(childBlock: SqlBlock): Boolean { + val isNewGroupType = childBlock.indent.indentLevel.isNewLineGroup() + val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory()?.second + val lastKeywordText = + if (lastGroup?.indent?.indentLevel == IndentType.JOIN) { + lastGroup.node.text + } else { + lastGroup + ?.childBlocks + ?.lastOrNull { it.node.elementType == SqlTypes.KEYWORD } + ?.node + ?.text ?: lastGroup?.node?.text ?: "" + } + + val isSetLineGroup = + SqlKeywordUtil.isSetLineKeyword( + childBlock.node.text, + lastKeywordText, + ) + + return isNewGroupType && !isSetLineGroup + } + + private fun isNewLineGroupBlock( + childBlock: SqlBlock, + child: ASTNode, + lastGroup: SqlBlock?, + ): Boolean { + val isNewGroupType = childBlock.indent.indentLevel.isNewLineGroup() + val lastKeywordText = + if (lastGroup?.indent?.indentLevel == IndentType.JOIN) { + lastGroup.node.text + } else { + lastGroup + ?.childBlocks + ?.lastOrNull { it.node.elementType == SqlTypes.KEYWORD } + ?.node + ?.text ?: lastGroup?.node?.text ?: "" + } + + val isSetLineGroup = + SqlKeywordUtil.isSetLineKeyword( + child.text, + lastKeywordText, + ) + if (isNewGroupType && !isSetLineGroup) { + if (lastGroup is SqlSubQueryGroupBlock) { + return (lastGroup.childBlocks.size > 1) + } + return true + } + return false + } + + protected open fun updateSearchKeywordLevelHistory( + childBlock: SqlBlock, + child: ASTNode, + ) { + val lastGroupBlock = blockBuilder.getLastGroupTopNodeIndexHistory()?.second + val lastIndentLevel = lastGroupBlock?.indent?.indentLevel + if (lastGroupBlock == null || lastIndentLevel == null) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups null + } + return + } + + when (childBlock) { + is SqlKeywordGroupBlock -> { + if (lastGroupBlock.indent.indentLevel == IndentType.SUB) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups lastGroupBlock + } + } else if (lastIndentLevel == childBlock.indent.indentLevel) { + blockBuilder.removeLastGroupTopNodeIndexHistory() + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups lastGroupBlock.parentBlock + } + } else if (lastIndentLevel < childBlock.indent.indentLevel) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } else { + if (lastIndentLevel == IndentType.JOIN && + SqlKeywordUtil.isSecondOptionKeyword(child.text) + ) { + // left,right < inner,outer < join + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + return + } + + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history + .lastOrNull { it.second.indent.indentLevel < childBlock.indent.indentLevel } + ?.second + } + } + } + + is SqlColumnGroupBlock -> { + when (lastIndentLevel) { + childBlock.indent.indentLevel -> { + blockBuilder.removeLastGroupTopNodeIndexHistory() + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups lastGroupBlock.parentBlock + } + } + else -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + } + } + + is SqlInlineGroupBlock -> { + // case-end + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlInlineSecondGroupBlock -> { + if (childBlock.isEndCase) { + val inlineIndex = + blockBuilder.getGroupTopNodeIndexByIndentType(IndentType.INLINE) + if (inlineIndex >= 0) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history[inlineIndex].second + } + blockBuilder.clearSubListGroupTopNodeIndexHistory(inlineIndex) + } + return + } + if (lastIndentLevel == IndentType.INLINE_SECOND) { + blockBuilder.removeLastGroupTopNodeIndexHistory() + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups lastGroupBlock.parentBlock + } + return + } + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlColumnBlock -> { + setParentGroups( + childBlock, + ) { history -> + val parentGroupBlock = history.last().second + if (parentGroupBlock is SqlColumnDefinitionRawGroupBlock && + parentGroupBlock.columnName != "," + ) { + parentGroupBlock.columnName = childBlock.node.text + val columnDefinition = parentGroupBlock.parentBlock as? SqlColumnDefinitionGroupBlock + if (columnDefinition != null && columnDefinition.alignmentColumnName.length < parentGroupBlock.columnName.length) { + columnDefinition.alignmentColumnName = parentGroupBlock.columnName + } + } + return@setParentGroups history.last().second + } + } + + is SqlColumnDefinitionRawGroupBlock -> { + if (lastGroupBlock is SqlColumnDefinitionRawGroupBlock) { + blockBuilder.removeLastGroupTopNodeIndexHistory() + } + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlWordBlock, is SqlOtherBlock, is SqlLineCommentBlock, is SqlBlockCommentBlock -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlSubQueryGroupBlock -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlRightPatternBlock -> { + val paramIndex = blockBuilder.getGroupTopNodeIndexByIndentType(IndentType.PARAM) + if (paramIndex >= 0) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history[paramIndex].second + } + blockBuilder.clearSubListGroupTopNodeIndexHistory(paramIndex) + return + } + + val leftIndex = blockBuilder.getGroupTopNodeIndexByIndentType(IndentType.SUB) + if (leftIndex >= 0) { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history[leftIndex].second + } + blockBuilder.clearSubListGroupTopNodeIndexHistory(leftIndex) + return + } + } + + is SqlElSymbolBlock -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + is SqlDataTypeBlock -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + + else -> { + setParentGroups( + childBlock, + ) { history -> + return@setParentGroups history.last().second + } + } + } + } + + private fun setParentGroups( + childBlock: SqlBlock, + getParentGroup: (MutableList>) -> SqlBlock?, + ) { + val parentGroup = + getParentGroup(blockBuilder.getGroupTopNodeIndexHistory() as MutableList>) + childBlock.setParentGroupBlock(parentGroup) + if (isNewGroup(childBlock) || + childBlock is SqlSubGroupBlock || + childBlock is SqlViewGroupBlock || + childBlock is SqlInlineGroupBlock || + childBlock is SqlInlineSecondGroupBlock || + childBlock is SqlColumnDefinitionRawGroupBlock + ) { + blockBuilder.addGroupTopNodeIndexHistory(Pair(blocks.size - 1, childBlock)) + // Set parent-child relationship and indent for preceding comment at beginning of block group + blockBuilder.updateCommentBlockIndent(childBlock) + } + } + + open fun createBlockIndentLen(): Int = 0 + + open fun getBlock(child: ASTNode): SqlBlock { + val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory()?.second + return when (child.elementType) { + SqlTypes.KEYWORD -> { + return getKeywordBlock(child) + } + + SqlTypes.DATATYPE -> SqlDataTypeBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.LEFT_PAREN -> { + if (lastGroup is SqlCreateKeywordGroupBlock && lastGroup.createType == CreateQueryType.TABLE) { + SqlColumnDefinitionGroupBlock(child, wrap, alignment, spacingBuilder) + } else if (lastGroup is SqlColumnDefinitionRawGroupBlock) { + SqlDataTypeParamBlock(child, wrap, alignment, spacingBuilder) + } else if (lastGroup is SqlInsertKeywordGroupBlock) { + SqlInsertColumnGroupBlock(child, wrap, alignment, spacingBuilder) + } else { + SqlSubQueryGroupBlock(child, wrap, alignment, spacingBuilder) + } + } + + SqlTypes.OTHER -> + return SqlOtherBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.DOT -> return SqlElDotBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.RIGHT_PAREN -> return SqlRightPatternBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.COMMA -> { + return when (lastGroup) { + is SqlColumnDefinitionGroupBlock, is SqlColumnDefinitionRawGroupBlock -> + SqlColumnDefinitionRawGroupBlock( + child, + wrap, + alignment, + spacingBuilder, + ) + + is SqlColumnGroupBlock, is SqlKeywordGroupBlock -> SqlColumnGroupBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlCommaBlock(child, wrap, alignment, spacingBuilder) + } + } + + SqlTypes.WORD -> { + when (lastGroup) { + is SqlKeywordGroupBlock -> { + when { + SqlKeywordUtil.isBeforeTableKeyword(lastGroup.node.text) -> + SqlTableBlock( + child, + wrap, + alignment, + spacingBuilder, + ) + + else -> SqlWordBlock(child, wrap, alignment, spacingBuilder) + } + } + + is SqlColumnDefinitionGroupBlock -> { + lastGroup.alignmentColumnName = child.text + SqlColumnDefinitionRawGroupBlock( + child, + wrap, + alignment, + spacingBuilder, + ) + } + + is SqlColumnDefinitionRawGroupBlock -> { + if (lastGroup.childBlocks.isEmpty()) { + lastGroup.columnName = child.text + SqlColumnBlock( + child, + wrap, + alignment, + spacingBuilder, + ) + } else { + SqlWordBlock(child, wrap, alignment, spacingBuilder) + } + } + + else -> SqlWordBlock(child, wrap, alignment, spacingBuilder) + } + } + + SqlTypes.BLOCK_COMMENT -> { + if (PsiTreeUtil.getChildOfType(child.psi, PsiComment::class.java) != null) { + return SqlBlockCommentBlock( + child, + wrap, + alignment, + spacingBuilder, + ) + } + return SqlElBlockCommentBlock( + child, + wrap, + alignment, + createBlockCommentSpacingBuilder(), + spacingBuilder, + ) + } + + SqlTypes.LINE_COMMENT -> + return SqlLineCommentBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.PLUS, SqlTypes.MINUS, SqlTypes.ASTERISK, SqlTypes.SLASH -> + return SqlElSymbolBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.LE, SqlTypes.LT, SqlTypes.EL_EQ, SqlTypes.EL_NE, SqlTypes.GE, SqlTypes.GT -> + return SqlElSymbolBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.STRING, SqlTypes.NUMBER, SqlTypes.BOOLEAN -> + return SqlLiteralBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlUnknownBlock(child, wrap, alignment, spacingBuilder) + } + } + + private fun getKeywordBlock(child: ASTNode): SqlBlock { + // Because we haven't yet set the parent-child relationship of the block, + // the parent group references groupTopNodeIndexHistory. + val indentLevel = SqlKeywordUtil.getIndentType(child.text) + val lastGroupBlock = blockBuilder.getLastGroupTopNodeIndexHistory()?.second + if (indentLevel.isNewLineGroup()) { + when (indentLevel) { + IndentType.JOIN -> { + return if (SqlKeywordUtil.isJoinKeyword(child.text)) { + SqlJoinGroupBlock(child, wrap, alignment, spacingBuilder) + } else if (lastGroupBlock is SqlJoinGroupBlock) { + SqlKeywordBlock(child, IndentType.ATTACHED, wrap, alignment, spacingBuilder) + } else { + SqlJoinGroupBlock(child, wrap, alignment, spacingBuilder) + } + } + + IndentType.INLINE_SECOND -> { + return SqlInlineSecondGroupBlock(child, wrap, alignment, spacingBuilder) + } + + IndentType.TOP -> { + if (child.text.lowercase() == "create") { + return SqlCreateKeywordGroupBlock(child, wrap, alignment, spacingBuilder) + } + if (child.text.lowercase() == "insert") { + return SqlInsertKeywordGroupBlock(child, wrap, alignment, spacingBuilder) + } + return SqlKeywordGroupBlock(child, indentLevel, wrap, alignment, spacingBuilder) + } + + else -> { + return SqlKeywordGroupBlock(child, indentLevel, wrap, alignment, spacingBuilder) + } + } + } + + when (indentLevel) { + IndentType.INLINE -> { + if (!SqlKeywordUtil.isSetLineKeyword( + child.text, + lastGroupBlock?.node?.text ?: "", + ) + ) { + return SqlInlineGroupBlock(child, wrap, alignment, spacingBuilder) + } + } + + IndentType.ATTACHED -> { + if (lastGroupBlock is SqlCreateKeywordGroupBlock) { + lastGroupBlock.setCreateQueryType(child.text) + return SqlKeywordBlock(child, indentLevel, wrap, alignment, spacingBuilder) + } + } + + IndentType.OPTIONS -> { + if (child.text.lowercase() == "as") { + val parentCreateBlock = + lastGroupBlock as? SqlCreateKeywordGroupBlock + ?: lastGroupBlock?.parentBlock as? SqlCreateKeywordGroupBlock + if (parentCreateBlock != null && parentCreateBlock.createType == CreateQueryType.VIEW) { + return SqlViewGroupBlock(child, wrap, alignment, spacingBuilder) + } + } + } + + else -> return SqlKeywordBlock(child, indentLevel, wrap, alignment, spacingBuilder) + } + return SqlKeywordBlock(child, indentLevel, wrap, alignment, spacingBuilder) + } + + protected open fun createSpacingBuilder(): SqlCustomSpacingBuilder = SqlCustomSpacingBuilder() + + protected fun createBlockCommentSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.BLOCK_COMMENT_START, + SqlTypes.BLOCK_COMMENT_CONTENT, + Spacing.createSpacing(0, 0, 0, true, 0), + ).withSpacing( + SqlTypes.BLOCK_COMMENT_CONTENT, + SqlTypes.BLOCK_COMMENT_END, + Spacing.createSpacing(0, 0, 0, true, 0), + ).withSpacing( + SqlTypes.EL_FIELD_ACCESS_EXPR, + SqlTypes.OTHER, + Spacing.createSpacing(1, 1, 0, false, 0), + ).withSpacing( + SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR, + SqlTypes.OTHER, + Spacing.createSpacing(1, 1, 0, false, 0), + ).withSpacing( + SqlTypes.BLOCK_COMMENT_CONTENT, + SqlTypes.BLOCK_COMMENT_START, + Spacing.createSpacing(0, 0, 0, true, 0), + ).withSpacing( + SqlTypes.BLOCK_COMMENT_CONTENT, + SqlTypes.BLOCK_COMMENT_END, + Spacing.createSpacing(0, 0, 0, true, 0), + ) + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? { + if (!isEnableFormat()) return null + // The end of a line comment element is a newline, so just add a space for the indent. + if (child1 is SqlLineCommentBlock) { + if (child2 is SqlBlock) { + return Spacing.createSpacing(child2.indent.indentLen, child2.indent.indentLen, 0, false, 0) + } + } + + // Do not leave a space after the comment block of the bind variable + if (child1 is SqlElBlockCommentBlock && child2 !is SqlCommentBlock) { + return Spacing.createSpacing(0, 0, 0, false, 0) + } + + if (child2 is SqlElBlockCommentBlock) { + when (child1) { + is SqlElBlockCommentBlock -> { + val indentLen = child2.indent.indentLen + return Spacing.createSpacing(indentLen, indentLen, 1, false, 0) + } + + is SqlWhitespaceBlock -> { + val indentLen = child2.indent.indentLen + return Spacing.createSpacing(indentLen, indentLen, 0, false, 0) + } + + else -> return SqlCustomSpacingBuilder.normalSpacing + } + } + + if (child1 is SqlWhitespaceBlock) { + when (child2) { + is SqlBlockCommentBlock, is SqlLineCommentBlock -> { + val indentLen = child2.indent.indentLen + return Spacing.createSpacing(indentLen, indentLen, 0, false, 0) + } + + is SqlNewGroupBlock -> { + return SqlCustomSpacingBuilder() + .getSpacing( + child2, + )?.let { return it } + } + } + } + + if (child2 is SqlNewGroupBlock) { + when (child2) { + is SqlSubQueryGroupBlock -> { + if (child1 is SqlNewGroupBlock) { + return SqlCustomSpacingBuilder.normalSpacing + } else { + // Remove spaces for parameter subgroups such as functions + SqlCustomSpacingBuilder.nonSpacing + } + } + + else -> { + SqlCustomSpacingBuilder.normalSpacing + } + } + } + + if (child2 is SqlColumnDefinitionRawGroupBlock) { + SqlCustomSpacingBuilder().getSpacingColumnDefinitionRaw(child2)?.let { return it } + } + + if (child2 is SqlRightPatternBlock) { + return when { + child2.parentBlock is SqlColumnDefinitionGroupBlock -> + SqlCustomSpacingBuilder() + .getSpacingColumnDefinitionRawEndRight(child2) + ?.let { return it } + child2.parentBlock is SqlDataTypeParamBlock -> SqlCustomSpacingBuilder.nonSpacing + + child2.preSpaceRight -> SqlCustomSpacingBuilder.normalSpacing + else -> SqlCustomSpacingBuilder.nonSpacing + } + } + + if (child1 is SqlBlock && (child2 is SqlCommaBlock || child2 is SqlColumnGroupBlock)) { + SqlCustomSpacingBuilder().getSpacingWithIndentComma(child1, child2)?.let { return it } + } + + if (child2 is SqlDataTypeParamBlock) { + return SqlCustomSpacingBuilder.nonSpacing + } + + if (child2 is SqlColumnBlock) { + SqlCustomSpacingBuilder().getSpacingColumnDefinition(child2)?.let { return it } + } + + val spacing: Spacing? = customSpacingBuilder?.getCustomSpacing(child1, child2) + return spacing ?: spacingBuilder.getSpacing(this, child1, child2) + } + + override fun getChildAttributes(newChildIndex: Int): ChildAttributes { + if (!isEnableFormat()) return ChildAttributes(Indent.getNoneIndent(), null) + + blocks + .getOrNull(newChildIndex) + ?.let { + val indent = + when (it) { + is SqlKeywordGroupBlock -> Indent.getSpaceIndent(it.indent.indentLen) + else -> childIndent ?: Indent.getNoneIndent() + } + return ChildAttributes(indent, null) + } + return ChildAttributes(Indent.getNoneIndent(), null) + } + + override fun getChildIndent(): Indent? = + if (!isEnableFormat()) { + Indent.getSpaceIndent(4) + } else { + Indent.getSpaceIndent(0) + } + + override fun isLeaf(): Boolean = myNode.firstChildNode == null +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlockCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlockCommentBlock.kt new file mode 100644 index 00000000..25f993e1 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlockCommentBlock.kt @@ -0,0 +1,33 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode + +open class SqlBlockCommentBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlCommentBlock( + node, + wrap, + alignment, + spacingBuilder, + ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlColumnBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlColumnBlock.kt new file mode 100644 index 00000000..f981f29e --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlColumnBlock.kt @@ -0,0 +1,65 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionGroupBlock + +class SqlColumnBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlWordBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + // Calculate right justification space during indentation after getting all column rows + indent.indentLen = 1 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun createBlockIndentLen(): Int { + parentBlock?.let { + val parentGroupDefinition = it.parentBlock as? SqlColumnDefinitionGroupBlock + if (parentGroupDefinition == null) return 1 + + val groupMaxAlimentLen = parentGroupDefinition.alignmentColumnName.length + val diffColumnName = groupMaxAlimentLen.minus(node.text.length) + return diffColumnName.plus(1) + } + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt new file mode 100644 index 00000000..fc65de27 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt @@ -0,0 +1,81 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlCreateKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInsertKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubGroupBlock + +open class SqlCommaBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.COMMA + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int { + parentBlock?.let { parent -> + if (parent is SqlSubGroupBlock) { + val parentIndentLen = parent.indent.groupIndentLen + val grand = parent.parentBlock + grand?.let { grand -> + if (grand is SqlCreateKeywordGroupBlock) { + val grandIndentLen = grand.indent.groupIndentLen + return grandIndentLen.plus(parentIndentLen).minus(1) + } + if (grand is SqlInsertKeywordGroupBlock) { + return parentIndentLen + } + if (grand is SqlColumnGroupBlock) { + val grandIndentLen = grand.indent.groupIndentLen + var prevTextLen = 1 + parent.prevChildren?.dropLast(1)?.forEach { prev -> prevTextLen = prevTextLen.plus(prev.node.text.length) } + return grandIndentLen.plus(prevTextLen).plus(1) + } + } + return parentIndentLen + } else { + val parentLen = parent.node.text.length + return parent.indent.groupIndentLen.plus(parentLen.plus(1)) + } + } + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommentBlock.kt new file mode 100644 index 00000000..c1aef0b5 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommentBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType + +abstract class SqlCommentBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlDataTypeBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlDataTypeBlock.kt new file mode 100644 index 00000000..c2720cc0 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlDataTypeBlock.kt @@ -0,0 +1,55 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType + +open class SqlDataTypeBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 1 + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt new file mode 100644 index 00000000..b6671d86 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt @@ -0,0 +1,94 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock + +open class SqlKeywordBlock( + node: ASTNode, + val indentLevel: IndentType = IndentType.ATTACHED, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlNewGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + indentLevel, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + + indent.indentLevel = indentLevel + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + // updateParentIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? { + if (parentBlock?.indent?.indentLevel == IndentType.SUB) { + return Indent.getIndent(Indent.Type.SPACES, 0, false, true) + } + return Indent.getNoneIndent() + } + + override fun createBlockIndentLen(): Int = + when (indentLevel) { + IndentType.TOP -> { + parentBlock?.let { + if (it.indent.indentLevel == IndentType.SUB) { + it.indent.groupIndentLen.plus(1) + } else { + 0 + } + } ?: 0 + } + + IndentType.SECOND -> { + parentBlock?.let { + it.indent.groupIndentLen + .plus(it.node.text.length) + .minus(this.node.text.length) + } ?: 1 + } + + IndentType.INLINE_SECOND -> { + parentBlock?.let { + it.indent.groupIndentLen + .plus(it.node.text.length) + .plus(1) + } ?: 1 + } + + else -> 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLineCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLineCommentBlock.kt new file mode 100644 index 00000000..a13b8c75 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLineCommentBlock.kt @@ -0,0 +1,62 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubQueryGroupBlock + +open class SqlLineCommentBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlCommentBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = super.getIndent() + + override fun isLeaf(): Boolean = true + + override fun createBlockIndentLen(): Int { + parentBlock?.let { + if (it is SqlSubQueryGroupBlock) { + if (it.childBlocks.dropLast(1).isEmpty()) { + return 1 + } + if (it.isFirstLineComment) { + return it.indent.groupIndentLen.minus(2) + } + } + return 1 + } + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLiteralBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLiteralBlock.kt new file mode 100644 index 00000000..eb01c727 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLiteralBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType + +open class SqlLiteralBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOperationBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOperationBlock.kt new file mode 100644 index 00000000..72935bb5 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOperationBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock + +class SqlOperationBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlElSymbolBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt new file mode 100644 index 00000000..953113bb --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt @@ -0,0 +1,57 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType + +open class SqlOtherBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt new file mode 100644 index 00000000..24642c18 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt @@ -0,0 +1,92 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlColumnDefinitionRawGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInsertColumnGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlInsertKeywordGroupBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +/** + * Parent is always a subclass of a subgroup + */ +open class SqlRightPatternBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + var preSpaceRight = false + + fun enableLastRight() { + parentBlock?.let { + if (it.node.treePrev.elementType == SqlTypes.WORD) { + preSpaceRight = false + return + } + if (it is SqlInsertColumnGroupBlock) { + preSpaceRight = false + return + } + it.parentBlock?.let { + preSpaceRight = ( + it.indent.indentLevel <= IndentType.SECOND && + it.parentBlock !is SqlInsertKeywordGroupBlock + ) || + it.indent.indentLevel == IndentType.JOIN + return + } + } + preSpaceRight = false + } + + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen ?: 1 + + override fun isLeaf(): Boolean = true + + fun isNeedBeforeWhiteSpace(lastGroup: SqlBlock?): Boolean = + lastGroup is SqlColumnDefinitionGroupBlock || + lastGroup is SqlColumnDefinitionRawGroupBlock +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlTableBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlTableBlock.kt new file mode 100644 index 00000000..4da3c03c --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlTableBlock.kt @@ -0,0 +1,36 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock + +class SqlTableBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlWordBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlUnknownBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlUnknownBlock.kt new file mode 100644 index 00000000..c5331e7c --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlUnknownBlock.kt @@ -0,0 +1,47 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType + +class SqlUnknownBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWhitespaceBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWhitespaceBlock.kt new file mode 100644 index 00000000..d96e53dd --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWhitespaceBlock.kt @@ -0,0 +1,50 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Indent +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock + +class SqlWhitespaceBlock( + node: ASTNode, + override var parentBlock: SqlBlock?, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getNoneIndent() + + override fun isLeaf(): Boolean = true + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = Spacing.createSpacing(0, 0, 0, false, 0) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWordBlock.kt new file mode 100644 index 00000000..aadd0335 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWordBlock.kt @@ -0,0 +1,77 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.group.SqlSubQueryGroupBlock + +open class SqlWordBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int { + parentBlock?.let { + when (it) { + is SqlSubQueryGroupBlock -> { + val parentIndentLen = it.indent.groupIndentLen + val grand = it.parentBlock + if (grand != null && grand.node.text.lowercase() == "create") { + val grandIndentLen = grand.indent.groupIndentLen + return grandIndentLen.plus(parentIndentLen).plus(1) + } + return parentIndentLen.plus(1) + } + + else -> { + val parentLen = it.node.text.length + return it.indent.groupIndentLen.plus(parentLen.plus(1)) + } + } + } + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElAtSignBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElAtSignBlock.kt new file mode 100644 index 00000000..202d89dd --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElAtSignBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +class SqlElAtSignBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = + customSpacingBuilder?.getCustomSpacing(child1, child2) ?: spacingBuilder.getSpacing( + this, + child1, + child2, + ) + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElBlockCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElBlockCommentBlock.kt new file mode 100644 index 00000000..02576645 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElBlockCommentBlock.kt @@ -0,0 +1,249 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.elementType +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.SqlCommentBlock +import org.domaframework.doma.intellij.formatter.block.SqlOperationBlock +import org.domaframework.doma.intellij.formatter.block.SqlUnknownBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlSubQueryGroupBlock +import org.domaframework.doma.intellij.psi.SqlElElseifDirective +import org.domaframework.doma.intellij.psi.SqlElForDirective +import org.domaframework.doma.intellij.psi.SqlElIfDirective +import org.domaframework.doma.intellij.psi.SqlTypes + +enum class SqlDirectiveType { + IF, + ELSEIF, + ELSE, + FOR, + END, + Variable, +} + +class SqlElBlockCommentBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + val customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlCommentBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + var isConditionLoopBlock = getConditionOrLoopBlock(node) + + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList { + val blocks = mutableListOf() + var child = node.firstChildNode + while (child != null) { + if (child !is PsiWhiteSpace) { + val block = getBlock(child) + blocks.add(block) + if (!isConditionLoopBlock && + ( + child.elementType == SqlTypes.EL_IF_DIRECTIVE || + child.elementType == SqlTypes.EL_FOR_DIRECTIVE || + child.elementType == SqlTypes.EL_ELSEIF_DIRECTIVE || + child.elementType == SqlTypes.EL_ELSE || + child.elementType == SqlTypes.EL_END + ) + ) { + isConditionLoopBlock = true + } + } + child = child.treeNext + } + return blocks + } + + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.GE, SqlTypes.LE, SqlTypes.GT, SqlTypes.LT, SqlTypes.EL_EQ, SqlTypes.EL_NE, + SqlTypes.PLUS, SqlTypes.MINUS, SqlTypes.ASTERISK, SqlTypes.SLASH, SqlTypes.AT_SIGN, + -> + SqlOperationBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.EL_FIELD_ACCESS_EXPR -> + SqlElFieldAccessBlock( + child, + wrap, + alignment, + createFieldAccessSpacingBuilder(), + spacingBuilder, + ) + + SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR -> + SqlElStaticFieldAccessBlock( + child, + wrap, + alignment, + createStaticFieldSpacingBuilder(), + spacingBuilder, + ) + + SqlTypes.EL_FUNCTION_CALL_EXPR -> + SqlElFunctionCallBlock( + child, + wrap, + alignment, + createSpacingBuilder(), + spacingBuilder, + ) + + SqlTypes.BLOCK_COMMENT_CONTENT -> + SqlBlockCommentBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlUnknownBlock(child, wrap, alignment, spacingBuilder) + } + + private fun getConditionOrLoopBlock(node: ASTNode): Boolean { + val directiveType = + when { + PsiTreeUtil.getChildOfType(node.psi, SqlElIfDirective::class.java) != null -> { + SqlDirectiveType.IF + } + + PsiTreeUtil.getChildOfType(node.psi, SqlElElseifDirective::class.java) != null -> { + SqlDirectiveType.ELSEIF + } + + PsiTreeUtil.getChildOfType(node.psi, SqlElForDirective::class.java) != null -> { + SqlDirectiveType.FOR + } + + PsiTreeUtil.getChildOfType(node.psi, SqlElElseifDirective::class.java) != null -> { + SqlDirectiveType.ELSE + } + + PsiTreeUtil.getChildOfType(node.psi, SqlElForDirective::class.java) != null -> { + SqlDirectiveType.END + } + + else -> { + val children = + PsiTreeUtil + .getChildrenOfType(node.psi, PsiElement::class.java) + ?.firstOrNull { it.elementType == SqlTypes.EL_ELSE || it.elementType == SqlTypes.EL_END } + children?.let { + when (it.elementType) { + SqlTypes.EL_ELSE -> SqlDirectiveType.ELSE + SqlTypes.EL_END -> SqlDirectiveType.END + else -> SqlDirectiveType.Variable + } + } ?: SqlDirectiveType.Variable + } + } + + return directiveType != SqlDirectiveType.Variable + } + + private fun createFieldAccessSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.EL_PRIMARY_EXPR, + SqlTypes.DOT, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.DOT, + SqlTypes.EL_IDENTIFIER, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.DOT, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.EL_PARAMETERS, + Spacing.createSpacing(0, 0, 0, false, 0), + ) + + private fun createStaticFieldSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.AT_SIGN, + SqlTypes.EL_CLASS, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.AT_SIGN, + SqlTypes.EL_IDENTIFIER, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.DOT, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.EL_PARAMETERS, + Spacing.createSpacing(0, 0, 0, false, 0), + ) + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = + customSpacingBuilder?.getCustomSpacing(child1, child2) ?: spacingBuilder.getSpacing( + this, + child1, + child2, + ) + + override fun isLeaf(): Boolean = false + + override fun createBlockIndentLen(): Int { + parentBlock?.let { + if (it is SqlSubQueryGroupBlock) { + if (it.childBlocks.dropLast(1).isEmpty()) { + return 1 + } + if (it.isFirstLineComment) { + return it.indent.groupIndentLen.minus(2) + } + } + } + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassBlock.kt new file mode 100644 index 00000000..550fef2a --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassBlock.kt @@ -0,0 +1,77 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlUnknownBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlElClassBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + customSpacingBuilder, + spacingBuilder, + ) { + override fun buildChildren(): MutableList { + val blocks = mutableListOf() + var child = node.firstChildNode + while (child != null) { + if (child !is PsiWhiteSpace) { + val block = getBlock(child) + blocks.add(block) + } + child = child.treeNext + } + return blocks + } + + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.EL_IDENTIFIER -> SqlElIdentifierBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.DOT -> SqlElDotBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlUnknownBlock(child, wrap, alignment, spacingBuilder) + } + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = + customSpacingBuilder?.getCustomSpacing(child1, child2) ?: spacingBuilder.getSpacing( + this, + child1, + child2, + ) + + override fun isLeaf(): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassRightBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassRightBlock.kt new file mode 100644 index 00000000..158807e8 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElClassRightBlock.kt @@ -0,0 +1,47 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +class SqlElClassRightBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = spacingBuilder.getSpacing(this, child1, child2) + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElCommaBlock.kt new file mode 100644 index 00000000..8ae4c6ea --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElCommaBlock.kt @@ -0,0 +1,36 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock + +open class SqlElCommaBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlElSymbolBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElDotBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElDotBlock.kt new file mode 100644 index 00000000..8aa0060b --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElDotBlock.kt @@ -0,0 +1,43 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock + +open class SqlElDotBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlElSymbolBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = spacingBuilder.getSpacing(this, child1, child2) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFieldAccessBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFieldAccessBlock.kt new file mode 100644 index 00000000..e2067976 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFieldAccessBlock.kt @@ -0,0 +1,80 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlElFieldAccessBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + customSpacingBuilder, + spacingBuilder, + ) { + override fun buildChildren(): MutableList { + val blocks = mutableListOf() + var child = node.firstChildNode + while (child != null) { + if (child !is PsiWhiteSpace) { + val block = getBlock(child) + blocks.add(block) + } + child = child.treeNext + } + return blocks + } + + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.EL_PRIMARY_EXPR -> { + SqlElPrimaryBlock(child, wrap, alignment, spacingBuilder) + } + + SqlTypes.DOT -> + SqlElDotBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.EL_IDENTIFIER -> + SqlElIdentifierBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.EL_PARAMETERS -> + SqlElParametersBlock(child, wrap, alignment, customSpacingBuilder, spacingBuilder) + + else -> SqlBlock(child, wrap, alignment, customSpacingBuilder, spacingBuilder) + } + + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = customSpacingBuilder?.getCustomSpacing(child1, child2) ?: spacingBuilder.getSpacing(this, child1, child2) + + override fun isLeaf(): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFunctionCallBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFunctionCallBlock.kt new file mode 100644 index 00000000..087569bc --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElFunctionCallBlock.kt @@ -0,0 +1,69 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlElFunctionCallBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + customSpacingBuilder, + spacingBuilder, + ) { + override fun buildChildren(): MutableList { + val blocks = mutableListOf() + var child = node.firstChildNode + while (child != null) { + if (child !is PsiWhiteSpace) { + val block = getBlock(child) + blocks.add(block) + } + child = child.treeNext + } + return blocks + } + + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.AT_SIGN -> + SqlElAtSignBlock(child, wrap, alignment, createSpacingBuilder(), spacingBuilder) + + SqlTypes.EL_IDENTIFIER -> + SqlElIdentifierBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.EL_PARAMETERS -> + SqlElParametersBlock(child, wrap, alignment, createSpacingBuilder(), spacingBuilder) + + else -> SqlBlock(child, wrap, alignment, createSpacingBuilder(), spacingBuilder) + } + + override fun isLeaf(): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElIdentifierBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElIdentifierBlock.kt new file mode 100644 index 00000000..9f559835 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElIdentifierBlock.kt @@ -0,0 +1,40 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +class SqlElIdentifierBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElParametersBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElParametersBlock.kt new file mode 100644 index 00000000..af62f66b --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElParametersBlock.kt @@ -0,0 +1,55 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlUnknownBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlElParametersBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + customSpacingBuilder, + spacingBuilder, + ) { + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN -> + SqlElSymbolBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.EL_PRIMARY_EXPR -> + SqlElPrimaryBlock(child, wrap, alignment, spacingBuilder) + + SqlTypes.COMMA -> + SqlElCommaBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlUnknownBlock(child, wrap, alignment, spacingBuilder) + } + + override fun isLeaf(): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElPrimaryBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElPrimaryBlock.kt new file mode 100644 index 00000000..9f79dbef --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElPrimaryBlock.kt @@ -0,0 +1,44 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Block +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +class SqlElPrimaryBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? = spacingBuilder.getSpacing(this, child1, child2) + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElStaticFieldAccessBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElStaticFieldAccessBlock.kt new file mode 100644 index 00000000..563efd6c --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElStaticFieldAccessBlock.kt @@ -0,0 +1,88 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Spacing +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.util.PsiTreeUtil +import org.domaframework.doma.intellij.formatter.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlUnknownBlock +import org.domaframework.doma.intellij.psi.SqlElClass +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlElStaticFieldAccessBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + customSpacingBuilder: SqlCustomSpacingBuilder?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + customSpacingBuilder, + spacingBuilder, + ) { + override fun getBlock(child: ASTNode): SqlBlock = + when (child.elementType) { + SqlTypes.AT_SIGN -> { + if (PsiTreeUtil.skipSiblingsBackward( + child.psi, + PsiWhiteSpace::class.java, + ) is SqlElClass + ) { + SqlElClassRightBlock(child, wrap, alignment, spacingBuilder) + } else { + SqlElAtSignBlock(child, wrap, alignment, null, spacingBuilder) + } + } + + SqlTypes.EL_CLASS -> + SqlElClassBlock(child, wrap, alignment, null, spacingBuilder) + + SqlTypes.EL_IDENTIFIER -> + SqlElIdentifierBlock(child, wrap, alignment, spacingBuilder) + + else -> SqlUnknownBlock(child, wrap, alignment, spacingBuilder) + } + + override fun createSpacingBuilder(): SqlCustomSpacingBuilder = + SqlCustomSpacingBuilder() + .withSpacing( + SqlTypes.AT_SIGN, + SqlTypes.EL_CLASS, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.AT_SIGN, + SqlTypes.EL_IDENTIFIER, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.DOT, + Spacing.createSpacing(0, 0, 0, false, 0), + ).withSpacing( + SqlTypes.EL_IDENTIFIER, + SqlTypes.EL_PARAMETERS, + Spacing.createSpacing(0, 0, 0, false, 0), + ) + + override fun isLeaf(): Boolean = false +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElSymbolBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElSymbolBlock.kt new file mode 100644 index 00000000..a50a59b5 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlElSymbolBlock.kt @@ -0,0 +1,55 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.expr + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlElSymbolBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.NONE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.NONE + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionGroupBlock.kt new file mode 100644 index 00000000..ff745c74 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionGroupBlock.kt @@ -0,0 +1,56 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +/** + * Column List Group Block attached to Create Table + */ +class SqlColumnDefinitionGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlSubGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + var alignmentColumnName = "" + + // TODO:Customize indentation + val offset = 2 + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = offset +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionRawGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionRawGroupBlock.kt new file mode 100644 index 00000000..2c7f323d --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnDefinitionRawGroupBlock.kt @@ -0,0 +1,85 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +/** + * Column definition group block in the column list group attached to Create Table + * The parent must be SqlColumnDefinitionGroupBlock + */ +class SqlColumnDefinitionRawGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) { + // TODO:Customize indentation within an inline group + val defaultOffset = 5 + val isFirstColumnRaw = node.elementType != SqlTypes.COMMA + + var columnName = node.text + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + /** + * Right-justify the longest column name in the column definition. + */ + override fun createBlockIndentLen(): Int { + if (!isFirstColumnRaw) return defaultOffset + + parentBlock?.let { + return when (it) { + is SqlColumnDefinitionGroupBlock -> { + getColumnRawNewIndent(it) + } + + else -> { + 1 + } + } + } + return 1 + } + + private fun getColumnRawNewIndent(groupRawBlock: SqlColumnDefinitionGroupBlock): Int { + val groupMaxAlimentLen = groupRawBlock.alignmentColumnName.length + val diffColumnName = groupMaxAlimentLen.minus(columnName.length) + val newSpaces = defaultOffset.plus(diffColumnName) + return newSpaces.plus(2) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnGroupBlock.kt new file mode 100644 index 00000000..fb25feef --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlColumnGroupBlock.kt @@ -0,0 +1,76 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +/** + * Group blocks when generating columns with subqueries + */ +class SqlColumnGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlSubGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + var isFirstColumnGroup = node.text != "," + + override val indent = + ElementIndent( + IndentType.COLUMN, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.COLUMN + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = + if (isFirstColumnGroup) indent.indentLen else indent.indentLen.plus(1) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = + parentBlock?.let { + if (it is SqlKeywordGroupBlock) { + val parentIndentLen = it.indent.indentLen.plus(it.node.text.length) + val subGroup = it.parentBlock as? SqlSubGroupBlock + if (subGroup is SqlSubGroupBlock && !subGroup.isFirstLineComment) { + parentIndentLen.plus(3) + } else { + parentIndentLen.plus(1) + } + } else { + it.indent.groupIndentLen.plus(1) + } + } ?: 1 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlCreateKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlCreateKeywordGroupBlock.kt new file mode 100644 index 00000000..81744982 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlCreateKeywordGroupBlock.kt @@ -0,0 +1,70 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.CreateQueryType +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlCreateKeywordGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlKeywordGroupBlock( + node, + IndentType.TOP, + wrap, + alignment, + spacingBuilder, + ) { + var createType: CreateQueryType = CreateQueryType.NONE + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.TOP + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? { + if (parentBlock?.indent?.indentLevel == IndentType.SUB) { + return Indent.getSpaceIndent(0) + } + return Indent.getNoneIndent() + } + + override fun createBlockIndentLen(): Int = + parentBlock?.let { + if (it.indent.indentLevel == IndentType.SUB) { + it.indent.groupIndentLen.plus(1) + } else { + 0 + } + } ?: 0 + + fun setCreateQueryType(nextKeyword: String) { + createType = CreateQueryType.getCreateTableType(nextKeyword) + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlDataTypeParamBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlDataTypeParamBlock.kt new file mode 100644 index 00000000..eae5008a --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlDataTypeParamBlock.kt @@ -0,0 +1,54 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +/** + * The parent must be [SqlColumnDefinitionRawGroupBlock] + */ +class SqlDataTypeParamBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlSubGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.PARAM, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.PARAM + indent.indentLen = 0 + indent.groupIndentLen = 0 + } + + override fun createBlockIndentLen(): Int = 0 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineGroupBlock.kt new file mode 100644 index 00000000..c3ab0844 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineGroupBlock.kt @@ -0,0 +1,67 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlInlineGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlNewGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.INLINE, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.INLINE + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? { + if (parentBlock?.indent?.indentLevel == IndentType.SUB) { + return Indent.getSpaceIndent(0) + } + return Indent.getNoneIndent() + } + + override fun createBlockIndentLen(): Int = + parentBlock?.let { + it.indent.groupIndentLen + .plus(it.node.text.length) + .plus(1) + } ?: 1 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineSecondGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineSecondGroupBlock.kt new file mode 100644 index 00000000..de0986b9 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInlineSecondGroupBlock.kt @@ -0,0 +1,68 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlInlineSecondGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlNewGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + val isEndCase = node.text.lowercase() == "end" + + override val indent = + ElementIndent( + IndentType.INLINE_SECOND, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.INLINE_SECOND + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = + parentBlock?.let { + // TODO:Customize indentation within an inline group + if (isEndCase) { + it.indent.indentLen + } else { + it.indent.groupIndentLen + .plus(it.node.text.length) + } + } ?: 1 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertColumnGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertColumnGroupBlock.kt new file mode 100644 index 00000000..044cb549 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertColumnGroupBlock.kt @@ -0,0 +1,87 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +/** + * Block of columns to insert + */ +class SqlInsertColumnGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlSubGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(1) + updateParentGroupIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int { + parentBlock?.let { + if (it is SqlInsertKeywordGroupBlock) { + var parentLen = 0 + val keywords = + it.childBlocks.dropLast(1).takeWhile { it.node.elementType == SqlTypes.KEYWORD } + keywords.forEach { keyword -> + parentLen = parentLen.plus(keyword.node.text.length).plus(1) + } + return it.indent.indentLen + .plus(it.node.text.length) + .plus(1) + .plus(parentLen) + } + // TODO:Customize indentation + return 2 + } ?: return 2 + } + + private fun updateParentGroupIndentLen() { + parentBlock?.let { + if (it is SqlInsertKeywordGroupBlock) { + var parentLen = 0 + val keywords = + it.childBlocks.dropLast(1).takeWhile { it.node.elementType == SqlTypes.KEYWORD } + keywords.forEach { keyword -> + parentLen = parentLen.plus(keyword.node.text.length).plus(1) + } + it.indent.groupIndentLen = + it.indent.indentLen + .plus(it.node.text.length) + .plus(parentLen) + } + } + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertKeywordGroupBlock.kt new file mode 100644 index 00000000..720d4a1e --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlInsertKeywordGroupBlock.kt @@ -0,0 +1,58 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlInsertKeywordGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlKeywordGroupBlock( + node, + IndentType.TOP, + wrap, + alignment, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.TOP + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = + parentBlock?.let { + if (it.indent.indentLevel == IndentType.SUB) { + it.indent.groupIndentLen.plus(1) + } else { + 0 + } + } ?: 0 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlJoinGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlJoinGroupBlock.kt new file mode 100644 index 00000000..31c806d0 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlJoinGroupBlock.kt @@ -0,0 +1,59 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlJoinGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlKeywordGroupBlock( + node, + IndentType.JOIN, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.JOIN, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + parentBlock = block + parentBlock?.childBlocks?.add(this) + indent.indentLevel = IndentType.JOIN + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen?.plus(1) ?: 1 +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlKeywordGroupBlock.kt new file mode 100644 index 00000000..3c34cf51 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlKeywordGroupBlock.kt @@ -0,0 +1,197 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.SqlKeywordUtil +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +open class SqlKeywordGroupBlock( + node: ASTNode, + val indentLevel: IndentType = IndentType.TOP, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlNewGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + indentLevel, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + val preChildBlock = block?.childBlocks?.dropLast(1)?.lastOrNull() + indent.indentLevel = indentLevel + + val baseIndentLen = getBaseIndentLen(preChildBlock, block) + indent.groupIndentLen = baseIndentLen.plus(node.text.length) + indent.indentLen = adjustIndentIfFirstChildIsLineComment(baseIndentLen) + createGroupIndentLen() + } + + private fun getBaseIndentLen( + preChildBlock: SqlBlock?, + block: SqlBlock?, + ): Int { + if (block == null) { + return createBlockIndentLen() + } + if (preChildBlock != null && + preChildBlock.indent.indentLevel == this.indent.indentLevel && + !SqlKeywordUtil.isSetLineKeyword(preChildBlock.node.text, block.node.text) + ) { + if (indent.indentLevel == IndentType.SECOND) { + val diffPreBlockTextLen = node.text.length.minus(preChildBlock.node.text.length) + return preChildBlock.indent.indentLen.minus(diffPreBlockTextLen) + } else { + return preChildBlock.indent.indentLen + } + } else { + return createBlockIndentLen() + } + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? { + if (parentBlock?.indent?.indentLevel == IndentType.SUB) { + return Indent.getSpaceIndent(0) + } + return Indent.getNoneIndent() + } + + /** + * Adjust the indent position of the subgroup block element itself if it has a comment + */ + private fun adjustIndentIfFirstChildIsLineComment(baseIndent: Int): Int { + parentBlock?.let { + if (indent.indentLevel == IndentType.TOP) { + return if (it is SqlSubGroupBlock) { + return if (it.isFirstLineComment) { + it.indent.groupIndentLen.minus(it.node.text.length) + } else { + val newIndentLen = baseIndent.minus(1) + return if (newIndentLen >= 0) newIndentLen else 0 + } + } else { + return baseIndent + } + } + } + return baseIndent + } + + override fun createBlockIndentLen(): Int { + when (indentLevel) { + IndentType.TOP -> { + parentBlock?.let { + val groupLen = it.indent.groupIndentLen + return if (it.indent.indentLevel == IndentType.FILE) { + 0 + } else { + groupLen + } + } ?: return 0 + } + + IndentType.SECOND -> { + parentBlock?.let { parent -> + val groupLen = parent.indent.groupIndentLen + + if (parent.indent.indentLevel == IndentType.FILE) { + return 0 + } else { + parent.parentBlock?.let { grand -> + return if (grand is SqlViewGroupBlock) { + groupLen.minus(this.node.text.length) + } else if (grand is SqlSubGroupBlock) { + groupLen.minus(node.text.length).plus(1) + } else { + groupLen.minus(this.node.text.length) + } + } ?: return groupLen.minus(this.node.text.length) + } + } ?: return 1 + } + + IndentType.SECOND_OPTION -> { + parentBlock?.let { + val groupLen = it.indent.groupIndentLen.plus(1) + if (it.indent.indentLevel == IndentType.FILE) { + return 0 + } + val subGroupBlock = it.parentBlock as? SqlSubGroupBlock + val newIndent = + if (it is SqlSubQueryGroupBlock) { + groupLen + } else if (it is SqlKeywordGroupBlock && subGroupBlock != null && subGroupBlock.isFirstLineComment) { + groupLen + } else { + var parentLen = 0 + val keywords = it.childBlocks.dropLast(1).takeWhile { it.node.elementType == SqlTypes.KEYWORD } + keywords.forEach { keyword -> + parentLen = parentLen.plus(keyword.node.text.length).plus(1) + } + val parentTextLen = it.indent.groupIndentLen.plus(parentLen) + return parentTextLen.minus(node.text.length) + } + return newIndent + } ?: 1 + } + + IndentType.INLINE_SECOND -> { + parentBlock?.let { + if (it.indent.indentLevel == IndentType.FILE) 0 + return it.indent.groupIndentLen + .plus(1) + } ?: return 1 + } + + else -> return 1 + } + return 1 + } + + private fun createGroupIndentLen(): Int { + parentBlock?.let { + if (indent.indentLevel == IndentType.SECOND_OPTION) { + var parentLen = 0 + val keywords = it.childBlocks.dropLast(1).filter { it.node.elementType == SqlTypes.KEYWORD } + keywords.forEach { keyword -> + parentLen = parentLen.plus(keyword.node.text.length).plus(1) + } + it.indent.groupIndentLen + .plus(parentLen) + .minus(node.text.length) + } + } ?: 1 + return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlNewGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlNewGroupBlock.kt new file mode 100644 index 00000000..5ecffd82 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlNewGroupBlock.kt @@ -0,0 +1,35 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +abstract class SqlNewGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlBlock( + node, + wrap, + alignment, + null, + spacingBuilder, + ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubGroupBlock.kt new file mode 100644 index 00000000..2c1a664b --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubGroupBlock.kt @@ -0,0 +1,67 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlCommentBlock + +abstract class SqlSubGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlNewGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + var isFirstLineComment = false + var prevChildren: List? = emptyList() + + override val indent = + ElementIndent( + IndentType.SUB, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + prevChildren = parentBlock?.childBlocks?.toList() + indent.indentLevel = indent.indentLevel + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = indent.indentLen.plus(node.text.length) + } + + override fun addChildBlock(childBlock: SqlBlock) { + childBlocks.add(childBlock) + if (!isFirstLineComment) { + isFirstLineComment = childBlock is SqlCommentBlock + } + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubQueryGroupBlock.kt new file mode 100644 index 00000000..2a737edc --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlSubQueryGroupBlock.kt @@ -0,0 +1,81 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlSubQueryGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlSubGroupBlock( + node, + wrap, + alignment, + spacingBuilder, + ) { + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = 1 + + private fun createGroupIndentLen(): Int { + parentBlock?.let { + if (it is SqlJoinGroupBlock) { + var parentLen = 0 + val keywords = + it.childBlocks.dropLast(1).takeWhile { it.node.elementType == SqlTypes.KEYWORD } + keywords.forEach { keyword -> + parentLen = parentLen.plus(keyword.node.text.length).plus(1) + } + return it.indent.indentLen + .plus(it.node.text.length) + .plus(2) + .plus(parentLen) + } else { + var parentLen = 0 + if (prevChildren?.findLast { it.indent.indentLevel == IndentType.COMMA } == null) { + prevChildren + ?.dropLast(1) + ?.forEach { prev -> + parentLen = parentLen.plus(prev.node.text.length).plus(1) + } + } + return it.indent.groupIndentLen + .plus(parentLen) + .plus(2) + } + return it.indent.groupIndentLen + .plus(2) + } ?: return 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlViewGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlViewGroupBlock.kt new file mode 100644 index 00000000..b878947e --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/SqlViewGroupBlock.kt @@ -0,0 +1,60 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.formatter.block.group + +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.SpacingBuilder +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.IndentType +import org.domaframework.doma.intellij.formatter.block.SqlBlock + +open class SqlViewGroupBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + spacingBuilder: SpacingBuilder, +) : SqlKeywordGroupBlock( + node, + IndentType.SECOND, + wrap, + alignment, + spacingBuilder, + ) { + override val indent = + ElementIndent( + IndentType.SECOND, + 0, + 0, + ) + + override fun setParentGroupBlock(block: SqlBlock?) { + super.setParentGroupBlock(block) + indent.indentLevel = IndentType.SUB + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = node.text.length + } + + override fun buildChildren(): MutableList = mutableListOf() + + override fun getIndent(): Indent? = Indent.getSpaceIndent(indent.indentLen) + + override fun createBlockIndentLen(): Int = parentBlock?.indent?.indentLen ?: 0 + + override fun isLeaf(): Boolean = true +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/inspector/SqlBindVariableValidInspector.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/inspector/SqlBindVariableValidInspector.kt index 29bd7fa2..53718969 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/inspector/SqlBindVariableValidInspector.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/inspector/SqlBindVariableValidInspector.kt @@ -142,9 +142,9 @@ class SqlBindVariableValidInspector : LocalInspectionTool() { PsiTreeUtil .getChildrenOfTypeAsList(element, PsiElement::class.java) .filter { - it.elementType != SqlTypes.EL_DOT && - it.elementType != SqlTypes.EL_LEFT_PAREN && - it.elementType != SqlTypes.EL_RIGHT_PAREN && + it.elementType != SqlTypes.DOT && + it.elementType != SqlTypes.LEFT_PAREN && + it.elementType != SqlTypes.RIGHT_PAREN && it.elementType != SqlTypes.EL_PARAMETERS }.toTypedArray() val topElm = blockElement.firstOrNull() as SqlElPrimaryExpr @@ -259,7 +259,7 @@ class SqlBindVariableValidInspector : LocalInspectionTool() { targetElement.firstChild?.elementType == SqlTypes.EL_CHAR || targetElement.firstChild?.elementType == SqlTypes.EL_NUMBER || targetElement.firstChild?.elementType == SqlTypes.EL_NULL || - targetElement.firstChild?.elementType == SqlTypes.EL_BOOLEAN || + targetElement.firstChild?.elementType == SqlTypes.BOOLEAN || targetElement.firstChild is SqlElNewExpr || targetElement.text.startsWith("@") ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt new file mode 100644 index 00000000..cdfbd415 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/DomaToolsConfigurable.kt @@ -0,0 +1,53 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.setting + +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.options.ConfigurationException +import org.domaframework.doma.intellij.state.DomaToolsFunctionEnableSettings +import java.util.Objects +import javax.swing.JComponent + +class DomaToolsConfigurable : Configurable { + private var mySettingsComponent: SettingComponent? = SettingComponent() + + override fun getDisplayName(): String = "Doma Tools" + + override fun createComponent(): JComponent? = mySettingsComponent?.panel + + override fun isModified(): Boolean { + val state: DomaToolsFunctionEnableSettings.State = + Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) + return mySettingsComponent?.enableFormat != state.isEnableSqlFormat + } + + @Throws(ConfigurationException::class) + override fun apply() { + val state: DomaToolsFunctionEnableSettings.State = + Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) + state.isEnableSqlFormat = mySettingsComponent?.enableFormat == true + } + + override fun reset() { + val state: DomaToolsFunctionEnableSettings.State = + Objects.requireNonNull(DomaToolsFunctionEnableSettings.getInstance().getState()) + mySettingsComponent?.enableFormat = state.isEnableSqlFormat + } + + override fun disposeUIResources() { + mySettingsComponent = null + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt b/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt new file mode 100644 index 00000000..9aa4c2c5 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/setting/SettingComponent.kt @@ -0,0 +1,42 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.setting + +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.FormBuilder +import org.domaframework.doma.intellij.bundle.MessageBundle +import javax.swing.JPanel + +class SettingComponent { + val panel: JPanel? + private val enableFormatCheckBox = JBCheckBox() + + init { + this.panel = + FormBuilder + .createFormBuilder() + .addLabeledComponent(JBLabel(MessageBundle.message("config.enable.sql.format")), enableFormatCheckBox, 1, false) + .addComponentFillVertically(JPanel(), 0) + .panel + } + + var enableFormat: Boolean + get() = enableFormatCheckBox.isSelected + set(enabled) { + enableFormatCheckBox.isSelected = enabled + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt b/src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt new file mode 100644 index 00000000..9f4c25a6 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/state/DomaToolsFunctionEnableSettings.kt @@ -0,0 +1,48 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.domaframework.doma.intellij.state + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage + +@Service(Service.Level.APP) +@State( + name = "org.domaframework.doma", + reloadable = true, + storages = [Storage("DomaTools.xml")], +) +class DomaToolsFunctionEnableSettings : PersistentStateComponent { + class State : BaseState() { + var isEnableSqlFormat = false + } + + var myState: State = State() + + override fun getState(): State = myState + + override fun loadState(state: DomaToolsFunctionEnableSettings.State) { + myState = state + } + + companion object { + fun getInstance(): DomaToolsFunctionEnableSettings = + ApplicationManager.getApplication().getService(DomaToolsFunctionEnableSettings::class.java) + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 83068dfa..16700250 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -18,6 +18,12 @@ + + + @@ -38,6 +44,12 @@ + + + + + ( + ThrowableRunnable { + CodeStyleManager.getInstance(project).reformatText( + currentFile, + arrayListOf(currentFile.textRange), + ) + }, + ) + myFixture.checkResultByFile(afterFile) + } +} diff --git a/src/test/testData/sql/formatter/CreateTable.sql b/src/test/testData/sql/formatter/CreateTable.sql new file mode 100644 index 00000000..b90e7bd4 --- /dev/null +++ b/src/test/testData/sql/formatter/CreateTable.sql @@ -0,0 +1,6 @@ +CREATE TABLE departments +( +id INT PRIMARY KEY + , name VARCHAR(100) + , loc INT NOT NULL +) diff --git a/src/test/testData/sql/formatter/CreateView.sql b/src/test/testData/sql/formatter/CreateView.sql new file mode 100644 index 00000000..494775b9 --- /dev/null +++ b/src/test/testData/sql/formatter/CreateView.sql @@ -0,0 +1,7 @@ +CREATE VIEW EmployeeView AS +SELECT employeeid + , firstname + , lastname + , hiredate + FROM employee + WHERE salary > 50000 \ No newline at end of file diff --git a/src/test/testData/sql/formatter/Delete.sql b/src/test/testData/sql/formatter/Delete.sql new file mode 100644 index 00000000..caeb7ddb --- /dev/null +++ b/src/test/testData/sql/formatter/Delete.sql @@ -0,0 +1 @@ +delete from x where id in (select id from x2 where id > /* id */ 101 and div = 't') diff --git a/src/test/testData/sql/formatter/FormattedCreateTable.sql b/src/test/testData/sql/formatter/FormattedCreateTable.sql new file mode 100644 index 00000000..cf9a670f --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedCreateTable.sql @@ -0,0 +1,6 @@ +CREATE TABLE departments + ( + id INT PRIMARY KEY + , name VARCHAR(100) + , loc INT NOT NULL + ) diff --git a/src/test/testData/sql/formatter/FormattedCreateView.sql b/src/test/testData/sql/formatter/FormattedCreateView.sql new file mode 100644 index 00000000..6b8ae76d --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedCreateView.sql @@ -0,0 +1,8 @@ +CREATE VIEW EmployeeView +AS + SELECT employeeid + , firstname + , lastname + , hiredate + FROM employee + WHERE salary > 50000 diff --git a/src/test/testData/sql/formatter/FormattedDelete.sql b/src/test/testData/sql/formatter/FormattedDelete.sql new file mode 100644 index 00000000..e802d411 --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedDelete.sql @@ -0,0 +1,5 @@ +DELETE FROM x + WHERE id IN ( SELECT id + FROM x2 + WHERE id > /* id */101 + AND div = 't' ) diff --git a/src/test/testData/sql/formatter/FormattedInsert.sql b/src/test/testData/sql/formatter/FormattedInsert.sql new file mode 100644 index 00000000..9a734d28 --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedInsert.sql @@ -0,0 +1,5 @@ +INSERT INTO employee + (id + , name) + VALUES ( /* employees.id */0 + , /* employees.name */'name') diff --git a/src/test/testData/sql/formatter/FormattedSelect.sql b/src/test/testData/sql/formatter/FormattedSelect.sql new file mode 100644 index 00000000..7c252b06 --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedSelect.sql @@ -0,0 +1,43 @@ +/** TopBlock */ +SELECT o.* + , ISNULL(nbor.nearest + , 999) AS nearest -- column Line comment + /** From */ + FROM ( -- SubGroupLine + SELECT p.objid + , p.psfmag_g - p.extinction_g + 5 * LOG(u.propermotion / 100.) + 5 AS rpm + , p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i) AS gi + , ISNULL(s.plate + , 0) AS plate + , ISNULL(s.mjd + , 0) AS mjd + , ISNULL(s.fiberid + , 0) AS fiberid + FROM phototag p + JOIN usno u + ON p.objid = u.objid + /** Join */ + LEFT OUTER JOIN specobj s + /** ON */ + ON p.objid = s.bestobjid + /** Where */ + WHERE p.TYPE = DBO.FPHOTOTYPE('Star') + AND (p.flags & DBO.FPHOTOFLAGS('EDGE')) = 0 + AND (p.psfmag_g - p.extinction_g) BETWEEN 15 AND 20 + AND u.propermotion > 2. + /** And Group */ + AND (p.psfmag_g - p.extinction_g + 5 * LOG(u.propermotion / 100.) + 5 > 16.136 + 2.727 * (p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i)) + OR p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i) < 0.) ) AS o + LEFT OUTER JOIN ( SELECT n.objid + , MIN(n.distance) AS nearest + FROM neighbors n + JOIN phototag x + ON n.neighborobjid = x.objid + WHERE n.TYPE = DBO.FPHOTOTYPE('Star') + AND n.MODE = DBO.FPHOTOMODE('Primary') + AND n.neighbormode = DBO.FPHOTOMODE('Primary') + AND (x.TYPE = DBO.FPHOTOTYPE('Star') + OR x.TYPE = DBO.FPHOTOTYPE('Galaxy')) + AND x.modelmag_g BETWEEN 10 AND 21 + GROUP BY n.objid ) AS nbor + ON o.objid = nbor.objid diff --git a/src/test/testData/sql/formatter/FormattedUpdate.sql b/src/test/testData/sql/formatter/FormattedUpdate.sql new file mode 100644 index 00000000..5fea96fe --- /dev/null +++ b/src/test/testData/sql/formatter/FormattedUpdate.sql @@ -0,0 +1,4 @@ +UPDATE USER + SET name = /* user.name */'name' + , rank = /*user.rank */3 + WHERE id = /* user.id */1 diff --git a/src/test/testData/sql/formatter/Insert.sql b/src/test/testData/sql/formatter/Insert.sql new file mode 100644 index 00000000..3675ab49 --- /dev/null +++ b/src/test/testData/sql/formatter/Insert.sql @@ -0,0 +1,3 @@ +INSERT INTO employee(id, name) + VALUES ( /* employees.id */0 + , /* employees.name */'name') diff --git a/src/test/testData/sql/formatter/Select.sql b/src/test/testData/sql/formatter/Select.sql new file mode 100644 index 00000000..1084a0f3 --- /dev/null +++ b/src/test/testData/sql/formatter/Select.sql @@ -0,0 +1,54 @@ +/** TopBlock */ +SELECT o.* + , ISNULL(nbor.nearest + , 999) AS nearest -- column Line comment + /** From */ + FROM ( -- SubGroupLine + SELECT p.objid + , p.psfmag_g - p.extinction_g + 5 * LOG(u.propermotion / 100.) + 5 AS rpm + , p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i) AS gi + , ISNULL(s.plate + , 0) AS plate + , ISNULL(s.mjd + , 0) AS mjd + , ISNULL(s.fiberid + , 0) AS fiberid + + FROM phototag p + + JOIN usno u + + ON p.objid = u.objid + /** Join */ + LEFT OUTER JOIN specobj s + /** ON */ + ON p.objid = s.bestobjid + /** Where */ + WHERE p.TYPE = DBO.FPHOTOTYPE('Star') + AND (p.flags & DBO.FPHOTOFLAGS('EDGE')) = 0 + + AND (p.psfmag_g - p.extinction_g) BETWEEN 15 AND 20 + AND u.propermotion > 2. + /** And Group */ + AND (p.psfmag_g - p.extinction_g + 5 * LOG(u.propermotion / 100.) + 5 > 16.136 + 2.727 * (p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i)) + + OR p.psfmag_g - p.extinction_g - (p.psfmag_i - p.extinction_i) < 0.) ) AS o + + LEFT OUTER JOIN ( SELECT n.objid + , MIN(n.distance) AS nearest + + FROM neighbors n + JOIN phototag x + ON n.neighborobjid = x.objid + + WHERE n.TYPE = DBO.FPHOTOTYPE('Star') + + AND n.MODE = DBO.FPHOTOMODE('Primary') + AND n.neighbormode = DBO.FPHOTOMODE('Primary') + AND (x.TYPE = DBO.FPHOTOTYPE('Star') + + OR x.TYPE = DBO.FPHOTOTYPE('Galaxy')) + AND x.modelmag_g BETWEEN 10 AND 21 + + GROUP BY n.objid ) AS nbor + ON o.objid = nbor.objid \ No newline at end of file diff --git a/src/test/testData/sql/formatter/Update.sql b/src/test/testData/sql/formatter/Update.sql new file mode 100644 index 00000000..a7c81f18 --- /dev/null +++ b/src/test/testData/sql/formatter/Update.sql @@ -0,0 +1,4 @@ +update user set +name = /* user.name */'name', +rank = /*user.rank */3 +where id = /* user.id */1 \ No newline at end of file diff --git a/src/test/testData/sql/parser/SQLParser.sql b/src/test/testData/sql/parser/SQLParser.sql index 0d3ba892..fdb177a4 100644 --- a/src/test/testData/sql/parser/SQLParser.sql +++ b/src/test/testData/sql/parser/SQLParser.sql @@ -1,40 +1,37 @@ -- Psi tree comparison test data for keywords, directives, bind variables, member accesses, and static property calls -SELECT - e.employee_id - , u.user_id - , u.user_name - , u.email - , e.department - , COUNT(pe.project_id) - , /* employee.numberOfProjects * 10 */ AS projectPoint -FROM user u -JOIN employee e - ON u.user_id = e.user_id -LEFT JOIN project_employee pe - ON e.employee_id = pe.employee_id -WHERE - e.user_id = /* user.userId */0 - AND e.employee_id = /* employee.employeeId */0 - AND e.join_date <= /* referenceDate */'2099/12/31' -/*%if @isNotBlank(employee.departmentId) */ - /*%if employee.departmentId.startsWith("200") */ - γ€€AND e.department_id = /* employee.departmentId */'dept' - /*%elseif employee.numberOfProjects >= 3 */ - AND pe.start_date <= CURRENT_DATE - AND pe.end_date >= CURRENT_DATE - /*%end*/ - /*%for child : employee.departmentId */ - /*%if child_has_next */ - AND pe.parent_project = /* child.projectId */0 - /*%for p : child.member */ - AND pe.type = /* @example.entity.StaticType@PARAM1.getValue() */'0' - /*%end */ - /*%end */ - /*%end */ - /*%end */ -GROUP BY - e.employee_id, - u.user_id, - u.user_name, - u.email, - e.department \ No newline at end of file +SELECT e.employee_id + , u.user_id + , u.user_name + , u.email + , e.department + , COUNT(pe.project_id) + , /* employee.numberOfProjects * 10 */AS projectPoint + FROM user u + JOIN employee e + ON u.user_id = e.user_id + LEFT JOIN project_employee pe + ON e.employee_id = pe.employee_id + WHERE e.user_id = /* user.userId */0 + AND e.employee_id = /* employee.employeeId */0 + AND e.join_date <= /* referenceDate + 1 */'2099/12/31' + /*%if @isNotBlank(employee.departmentId) */ + /*%if employee.departmentId.startsWith("200") */γ€€ + AND e.department_id = /* employee.departmentId + 1 */'dept' + /*%elseif employee.numberOfProjects >= 3 */ + AND pe.start_date <= CURRENT_DATE + AND pe.end_date >= CURRENT_DATE + /*%end*/ + /*%for child : employee.departmentId */ + /*%if child_has_next */ + AND pe.parent_project = /* child.projectId */0 + /*%for p : child.member */ + AND pe.type = /* @example.entity.StaticType@PARAM1.getValue() */'0' + /*%end */ + /*%end */ + /*%end */ + /*%end */ + GROUP BY e.employee_id + , u.user_id + , u.user_name + , u.email + , e.department \ No newline at end of file diff --git a/src/test/testData/sql/parser/SQLParser.txt b/src/test/testData/sql/parser/SQLParser.txt index 81ad6deb..4c1546f8 100644 --- a/src/test/testData/sql/parser/SQLParser.txt +++ b/src/test/testData/sql/parser/SQLParser.txt @@ -1,417 +1,429 @@ -SQL File(0,1259) +SQL File(0,1275) PsiElement(SqlTokenType.LINE_COMMENT)('-- Psi tree comparison test data for keywords, directives, bind variables, member accesses, and static property calls\n')(0,118) PsiElement(SqlTokenType.KEYWORD)('SELECT')(118,124) - PsiWhiteSpace('\n ')(124,129) - PsiElement(SqlTokenType.WORD)('e')(129,130) - PsiElement(SqlTokenType.OTHER)('.')(130,131) - PsiElement(SqlTokenType.WORD)('employee_id')(131,142) - PsiWhiteSpace('\n ')(142,147) - PsiElement(SqlTokenType.OTHER)(',')(147,148) - PsiWhiteSpace(' ')(148,149) - PsiElement(SqlTokenType.WORD)('u')(149,150) - PsiElement(SqlTokenType.OTHER)('.')(150,151) - PsiElement(SqlTokenType.WORD)('user_id')(151,158) - PsiWhiteSpace('\n ')(158,163) - PsiElement(SqlTokenType.OTHER)(',')(163,164) - PsiWhiteSpace(' ')(164,165) - PsiElement(SqlTokenType.WORD)('u')(165,166) - PsiElement(SqlTokenType.OTHER)('.')(166,167) - PsiElement(SqlTokenType.WORD)('user_name')(167,176) - PsiWhiteSpace('\n ')(176,181) - PsiElement(SqlTokenType.OTHER)(',')(181,182) - PsiWhiteSpace(' ')(182,183) - PsiElement(SqlTokenType.WORD)('u')(183,184) - PsiElement(SqlTokenType.OTHER)('.')(184,185) - PsiElement(SqlTokenType.WORD)('email')(185,190) - PsiWhiteSpace('\n ')(190,195) - PsiElement(SqlTokenType.OTHER)(',')(195,196) - PsiWhiteSpace(' ')(196,197) - PsiElement(SqlTokenType.WORD)('e')(197,198) - PsiElement(SqlTokenType.OTHER)('.')(198,199) - PsiElement(SqlTokenType.WORD)('department')(199,209) - PsiWhiteSpace('\n ')(209,214) - PsiElement(SqlTokenType.OTHER)(',')(214,215) - PsiWhiteSpace(' ')(215,216) - PsiElement(SqlTokenType.WORD)('COUNT')(216,221) - PsiElement(SqlTokenType.OTHER)('(')(221,222) - PsiElement(SqlTokenType.WORD)('pe')(222,224) - PsiElement(SqlTokenType.OTHER)('.')(224,225) - PsiElement(SqlTokenType.WORD)('project_id')(225,235) - PsiElement(SqlTokenType.OTHER)(')')(235,236) - PsiWhiteSpace('\n ')(236,241) - PsiElement(SqlTokenType.OTHER)(',')(241,242) - PsiWhiteSpace(' ')(242,243) - SqlBlockCommentImpl(BLOCK_COMMENT)(243,279) - PsiElement(SqlTokenType./*)('/*')(243,245) - PsiWhiteSpace(' ')(245,246) - SqlElMultiplyExprImpl(EL_MULTIPLY_EXPR)(246,276) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(246,271) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(246,254) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(246,254) - PsiElement(SqlTokenType..)('.')(254,255) - PsiElement(SqlTokenType.EL_IDENTIFIER)('numberOfProjects')(255,271) - PsiWhiteSpace(' ')(271,272) - PsiElement(SqlTokenType.*)('*')(272,273) - PsiWhiteSpace(' ')(273,274) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(274,276) - PsiElement(SqlTokenType.EL_NUMBER)('10')(274,276) - PsiWhiteSpace(' ')(276,277) - PsiElement(SqlTokenType.*/)('*/')(277,279) - PsiWhiteSpace(' ')(279,280) - PsiElement(SqlTokenType.WORD)('AS')(280,282) - PsiWhiteSpace(' ')(282,283) - PsiElement(SqlTokenType.WORD)('projectPoint')(283,295) - PsiWhiteSpace('\n')(295,296) - PsiElement(SqlTokenType.KEYWORD)('FROM')(296,300) - PsiWhiteSpace(' ')(300,301) - PsiElement(SqlTokenType.WORD)('user')(301,305) - PsiWhiteSpace(' ')(305,306) - PsiElement(SqlTokenType.WORD)('u')(306,307) - PsiWhiteSpace('\n')(307,308) - PsiElement(SqlTokenType.KEYWORD)('JOIN')(308,312) - PsiWhiteSpace(' ')(312,313) - PsiElement(SqlTokenType.WORD)('employee')(313,321) + PsiWhiteSpace(' ')(124,125) + PsiElement(SqlTokenType.WORD)('e')(125,126) + PsiElement(SqlTokenType..)('.')(126,127) + PsiElement(SqlTokenType.WORD)('employee_id')(127,138) + PsiWhiteSpace('\n ')(138,146) + PsiElement(SqlTokenType.,)(',')(146,147) + PsiWhiteSpace(' ')(147,148) + PsiElement(SqlTokenType.WORD)('u')(148,149) + PsiElement(SqlTokenType..)('.')(149,150) + PsiElement(SqlTokenType.WORD)('user_id')(150,157) + PsiWhiteSpace('\n ')(157,165) + PsiElement(SqlTokenType.,)(',')(165,166) + PsiWhiteSpace(' ')(166,167) + PsiElement(SqlTokenType.WORD)('u')(167,168) + PsiElement(SqlTokenType..)('.')(168,169) + PsiElement(SqlTokenType.WORD)('user_name')(169,178) + PsiWhiteSpace('\n ')(178,186) + PsiElement(SqlTokenType.,)(',')(186,187) + PsiWhiteSpace(' ')(187,188) + PsiElement(SqlTokenType.WORD)('u')(188,189) + PsiElement(SqlTokenType..)('.')(189,190) + PsiElement(SqlTokenType.WORD)('email')(190,195) + PsiWhiteSpace('\n ')(195,203) + PsiElement(SqlTokenType.,)(',')(203,204) + PsiWhiteSpace(' ')(204,205) + PsiElement(SqlTokenType.WORD)('e')(205,206) + PsiElement(SqlTokenType..)('.')(206,207) + PsiElement(SqlTokenType.WORD)('department')(207,217) + PsiWhiteSpace('\n ')(217,225) + PsiElement(SqlTokenType.,)(',')(225,226) + PsiWhiteSpace(' ')(226,227) + PsiElement(SqlTokenType.WORD)('COUNT')(227,232) + PsiElement(SqlTokenType.()('(')(232,233) + PsiElement(SqlTokenType.WORD)('pe')(233,235) + PsiElement(SqlTokenType..)('.')(235,236) + PsiElement(SqlTokenType.WORD)('project_id')(236,246) + PsiElement(SqlTokenType.))(')')(246,247) + PsiWhiteSpace('\n ')(247,255) + PsiElement(SqlTokenType.,)(',')(255,256) + PsiWhiteSpace(' ')(256,257) + SqlBlockCommentImpl(BLOCK_COMMENT)(257,294) + PsiElement(SqlTokenType./*)('/*')(257,259) + PsiWhiteSpace(' ')(259,260) + SqlElMultiplyExprImpl(EL_MULTIPLY_EXPR)(260,291) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(260,285) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(260,268) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(260,268) + PsiElement(SqlTokenType..)('.')(268,269) + PsiElement(SqlTokenType.EL_IDENTIFIER)('numberOfProjects')(269,285) + PsiWhiteSpace(' ')(285,286) + PsiElement(SqlTokenType.*)('*')(286,287) + PsiWhiteSpace(' ')(287,289) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(289,291) + PsiElement(SqlTokenType.EL_NUMBER)('10')(289,291) + PsiWhiteSpace(' ')(291,292) + PsiElement(SqlTokenType.*/)('*/')(292,294) + PsiElement(SqlTokenType.KEYWORD)('AS')(294,296) + PsiWhiteSpace(' ')(296,297) + PsiElement(SqlTokenType.WORD)('projectPoint')(297,309) + PsiWhiteSpace('\n ')(309,312) + PsiElement(SqlTokenType.KEYWORD)('FROM')(312,316) + PsiWhiteSpace(' ')(316,317) + PsiElement(SqlTokenType.WORD)('user')(317,321) PsiWhiteSpace(' ')(321,322) - PsiElement(SqlTokenType.WORD)('e')(322,323) - PsiWhiteSpace('\n ')(323,328) - PsiElement(SqlTokenType.KEYWORD)('ON')(328,330) - PsiWhiteSpace(' ')(330,331) - PsiElement(SqlTokenType.WORD)('u')(331,332) - PsiElement(SqlTokenType.OTHER)('.')(332,333) - PsiElement(SqlTokenType.WORD)('user_id')(333,340) - PsiWhiteSpace(' ')(340,341) - PsiElement(SqlTokenType.OTHER)('=')(341,342) - PsiWhiteSpace(' ')(342,343) - PsiElement(SqlTokenType.WORD)('e')(343,344) - PsiElement(SqlTokenType.OTHER)('.')(344,345) - PsiElement(SqlTokenType.WORD)('user_id')(345,352) - PsiWhiteSpace('\n')(352,353) - PsiElement(SqlTokenType.KEYWORD)('LEFT')(353,357) - PsiWhiteSpace(' ')(357,358) - PsiElement(SqlTokenType.KEYWORD)('JOIN')(358,362) - PsiWhiteSpace(' ')(362,363) - PsiElement(SqlTokenType.WORD)('project_employee')(363,379) - PsiWhiteSpace(' ')(379,380) - PsiElement(SqlTokenType.WORD)('pe')(380,382) - PsiWhiteSpace('\n ')(382,387) - PsiElement(SqlTokenType.KEYWORD)('ON')(387,389) - PsiWhiteSpace(' ')(389,390) - PsiElement(SqlTokenType.WORD)('e')(390,391) - PsiElement(SqlTokenType.OTHER)('.')(391,392) - PsiElement(SqlTokenType.WORD)('employee_id')(392,403) - PsiWhiteSpace(' ')(403,404) - PsiElement(SqlTokenType.OTHER)('=')(404,405) - PsiWhiteSpace(' ')(405,406) - PsiElement(SqlTokenType.WORD)('pe')(406,408) - PsiElement(SqlTokenType.OTHER)('.')(408,409) - PsiElement(SqlTokenType.WORD)('employee_id')(409,420) - PsiWhiteSpace('\n')(420,421) - PsiElement(SqlTokenType.KEYWORD)('WHERE')(421,426) - PsiWhiteSpace('\n ')(426,431) - PsiElement(SqlTokenType.WORD)('e')(431,432) - PsiElement(SqlTokenType.OTHER)('.')(432,433) - PsiElement(SqlTokenType.WORD)('user_id')(433,440) - PsiWhiteSpace(' ')(440,441) - PsiElement(SqlTokenType.OTHER)('=')(441,442) - PsiWhiteSpace(' ')(442,443) - SqlBlockCommentImpl(BLOCK_COMMENT)(443,460) - PsiElement(SqlTokenType./*)('/*')(443,445) - PsiWhiteSpace(' ')(445,446) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(446,457) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(446,450) - PsiElement(SqlTokenType.EL_IDENTIFIER)('user')(446,450) - PsiElement(SqlTokenType..)('.')(450,451) - PsiElement(SqlTokenType.EL_IDENTIFIER)('userId')(451,457) - PsiWhiteSpace(' ')(457,458) - PsiElement(SqlTokenType.*/)('*/')(458,460) - PsiElement(SqlTokenType.NUMBER)('0')(460,461) - PsiWhiteSpace('\n ')(461,466) - PsiElement(SqlTokenType.KEYWORD)('AND')(466,469) - PsiWhiteSpace(' ')(469,470) - PsiElement(SqlTokenType.WORD)('e')(470,471) - PsiElement(SqlTokenType.OTHER)('.')(471,472) - PsiElement(SqlTokenType.WORD)('employee_id')(472,483) - PsiWhiteSpace(' ')(483,484) - PsiElement(SqlTokenType.OTHER)('=')(484,485) - PsiWhiteSpace(' ')(485,486) - SqlBlockCommentImpl(BLOCK_COMMENT)(486,511) - PsiElement(SqlTokenType./*)('/*')(486,488) - PsiWhiteSpace(' ')(488,489) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(489,508) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(489,497) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(489,497) - PsiElement(SqlTokenType..)('.')(497,498) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employeeId')(498,508) - PsiWhiteSpace(' ')(508,509) - PsiElement(SqlTokenType.*/)('*/')(509,511) - PsiElement(SqlTokenType.NUMBER)('0')(511,512) - PsiWhiteSpace('\n ')(512,517) - PsiElement(SqlTokenType.KEYWORD)('AND')(517,520) - PsiWhiteSpace(' ')(520,521) - PsiElement(SqlTokenType.WORD)('e')(521,522) - PsiElement(SqlTokenType.OTHER)('.')(522,523) - PsiElement(SqlTokenType.WORD)('join_date')(523,532) - PsiWhiteSpace(' ')(532,533) - PsiElement(SqlTokenType.OTHER)('<')(533,534) - PsiElement(SqlTokenType.OTHER)('=')(534,535) - PsiWhiteSpace(' ')(535,536) - SqlBlockCommentImpl(BLOCK_COMMENT)(536,555) - PsiElement(SqlTokenType./*)('/*')(536,538) - PsiWhiteSpace(' ')(538,539) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(539,552) - PsiElement(SqlTokenType.EL_IDENTIFIER)('referenceDate')(539,552) - PsiWhiteSpace(' ')(552,553) - PsiElement(SqlTokenType.*/)('*/')(553,555) - PsiElement(SqlTokenType.STRING)(''2099/12/31'')(555,567) - PsiWhiteSpace('\n')(567,568) - SqlBlockCommentImpl(BLOCK_COMMENT)(568,611) - PsiElement(SqlTokenType./*)('/*')(568,570) - SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(570,608) - PsiElement(SqlTokenType.%if)('%if')(570,573) - PsiWhiteSpace(' ')(573,574) - SqlElFunctionCallExprImpl(EL_FUNCTION_CALL_EXPR)(574,608) - PsiElement(SqlTokenType.@)('@')(574,575) - PsiElement(SqlTokenType.EL_IDENTIFIER)('isNotBlank')(575,585) - SqlElParametersImpl(EL_PARAMETERS)(585,608) - PsiElement(SqlTokenType.()('(')(585,586) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(586,607) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(586,594) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(586,594) - PsiElement(SqlTokenType..)('.')(594,595) - PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(595,607) - PsiElement(SqlTokenType.))(')')(607,608) - PsiWhiteSpace(' ')(608,609) - PsiElement(SqlTokenType.*/)('*/')(609,611) - PsiWhiteSpace('\n ')(611,616) - SqlBlockCommentImpl(BLOCK_COMMENT)(616,664) - PsiElement(SqlTokenType./*)('/*')(616,618) - SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(618,661) - PsiElement(SqlTokenType.%if)('%if')(618,621) - PsiWhiteSpace(' ')(621,622) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(622,661) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(622,630) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(622,630) - PsiElement(SqlTokenType..)('.')(630,631) - PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(631,643) - PsiElement(SqlTokenType..)('.')(643,644) - PsiElement(SqlTokenType.EL_IDENTIFIER)('startsWith')(644,654) - SqlElParametersImpl(EL_PARAMETERS)(654,661) - PsiElement(SqlTokenType.()('(')(654,655) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(655,660) - PsiElement(SqlTokenType.EL_STRING)('"200"')(655,660) - PsiElement(SqlTokenType.))(')')(660,661) - PsiWhiteSpace(' ')(661,662) - PsiElement(SqlTokenType.*/)('*/')(662,664) - PsiWhiteSpace('\n ')(664,671) - PsiElement(SqlTokenType.OTHER)('γ€€')(671,672) - PsiElement(SqlTokenType.KEYWORD)('AND')(672,675) - PsiWhiteSpace(' ')(675,676) - PsiElement(SqlTokenType.WORD)('e')(676,677) - PsiElement(SqlTokenType.OTHER)('.')(677,678) - PsiElement(SqlTokenType.WORD)('department_id')(678,691) - PsiWhiteSpace(' ')(691,692) - PsiElement(SqlTokenType.OTHER)('=')(692,693) - PsiWhiteSpace(' ')(693,694) - SqlBlockCommentImpl(BLOCK_COMMENT)(694,721) - PsiElement(SqlTokenType./*)('/*')(694,696) - PsiWhiteSpace(' ')(696,697) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(697,718) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(697,705) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(697,705) - PsiElement(SqlTokenType..)('.')(705,706) - PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(706,718) - PsiWhiteSpace(' ')(718,719) - PsiElement(SqlTokenType.*/)('*/')(719,721) - PsiElement(SqlTokenType.STRING)(''dept'')(721,727) - PsiWhiteSpace('\n ')(727,732) - SqlBlockCommentImpl(BLOCK_COMMENT)(732,775) - PsiElement(SqlTokenType./*)('/*')(732,734) - SqlElElseifDirectiveImpl(EL_ELSEIF_DIRECTIVE)(734,772) - PsiElement(SqlTokenType.%elseif)('%elseif')(734,741) - PsiWhiteSpace(' ')(741,742) - SqlElGeExprImpl(EL_GE_EXPR)(742,772) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(742,767) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(742,750) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(742,750) - PsiElement(SqlTokenType..)('.')(750,751) - PsiElement(SqlTokenType.EL_IDENTIFIER)('numberOfProjects')(751,767) - PsiWhiteSpace(' ')(767,768) - PsiElement(SqlTokenType.>=)('>=')(768,770) - PsiWhiteSpace(' ')(770,771) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(771,772) - PsiElement(SqlTokenType.EL_NUMBER)('3')(771,772) - PsiWhiteSpace(' ')(772,773) - PsiElement(SqlTokenType.*/)('*/')(773,775) - PsiWhiteSpace('\n ')(775,784) - PsiElement(SqlTokenType.KEYWORD)('AND')(784,787) - PsiWhiteSpace(' ')(787,788) - PsiElement(SqlTokenType.WORD)('pe')(788,790) - PsiElement(SqlTokenType.OTHER)('.')(790,791) - PsiElement(SqlTokenType.WORD)('start_date')(791,801) - PsiWhiteSpace(' ')(801,802) - PsiElement(SqlTokenType.OTHER)('<')(802,803) - PsiElement(SqlTokenType.OTHER)('=')(803,804) - PsiWhiteSpace(' ')(804,805) - PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(805,817) - PsiWhiteSpace('\n ')(817,826) + PsiElement(SqlTokenType.WORD)('u')(322,323) + PsiWhiteSpace('\n ')(323,331) + PsiElement(SqlTokenType.KEYWORD)('JOIN')(331,335) + PsiWhiteSpace(' ')(335,336) + PsiElement(SqlTokenType.WORD)('employee')(336,344) + PsiWhiteSpace(' ')(344,345) + PsiElement(SqlTokenType.WORD)('e')(345,346) + PsiWhiteSpace('\n ')(346,356) + PsiElement(SqlTokenType.KEYWORD)('ON')(356,358) + PsiWhiteSpace(' ')(358,359) + PsiElement(SqlTokenType.WORD)('u')(359,360) + PsiElement(SqlTokenType..)('.')(360,361) + PsiElement(SqlTokenType.WORD)('user_id')(361,368) + PsiWhiteSpace(' ')(368,369) + PsiElement(SqlTokenType.OTHER)('=')(369,370) + PsiWhiteSpace(' ')(370,371) + PsiElement(SqlTokenType.WORD)('e')(371,372) + PsiElement(SqlTokenType..)('.')(372,373) + PsiElement(SqlTokenType.WORD)('user_id')(373,380) + PsiWhiteSpace('\n ')(380,388) + PsiElement(SqlTokenType.KEYWORD)('LEFT')(388,392) + PsiWhiteSpace(' ')(392,393) + PsiElement(SqlTokenType.KEYWORD)('JOIN')(393,397) + PsiWhiteSpace(' ')(397,398) + PsiElement(SqlTokenType.WORD)('project_employee')(398,414) + PsiWhiteSpace(' ')(414,415) + PsiElement(SqlTokenType.WORD)('pe')(415,417) + PsiWhiteSpace('\n ')(417,432) + PsiElement(SqlTokenType.KEYWORD)('ON')(432,434) + PsiWhiteSpace(' ')(434,435) + PsiElement(SqlTokenType.WORD)('e')(435,436) + PsiElement(SqlTokenType..)('.')(436,437) + PsiElement(SqlTokenType.WORD)('employee_id')(437,448) + PsiWhiteSpace(' ')(448,449) + PsiElement(SqlTokenType.OTHER)('=')(449,450) + PsiWhiteSpace(' ')(450,451) + PsiElement(SqlTokenType.WORD)('pe')(451,453) + PsiElement(SqlTokenType..)('.')(453,454) + PsiElement(SqlTokenType.WORD)('employee_id')(454,465) + PsiWhiteSpace('\n ')(465,467) + PsiElement(SqlTokenType.KEYWORD)('WHERE')(467,472) + PsiWhiteSpace(' ')(472,473) + PsiElement(SqlTokenType.WORD)('e')(473,474) + PsiElement(SqlTokenType..)('.')(474,475) + PsiElement(SqlTokenType.WORD)('user_id')(475,482) + PsiWhiteSpace(' ')(482,483) + PsiElement(SqlTokenType.OTHER)('=')(483,484) + PsiWhiteSpace(' ')(484,485) + SqlBlockCommentImpl(BLOCK_COMMENT)(485,502) + PsiElement(SqlTokenType./*)('/*')(485,487) + PsiWhiteSpace(' ')(487,488) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(488,499) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(488,492) + PsiElement(SqlTokenType.EL_IDENTIFIER)('user')(488,492) + PsiElement(SqlTokenType..)('.')(492,493) + PsiElement(SqlTokenType.EL_IDENTIFIER)('userId')(493,499) + PsiWhiteSpace(' ')(499,500) + PsiElement(SqlTokenType.*/)('*/')(500,502) + PsiElement(SqlTokenType.NUMBER)('0')(502,503) + PsiWhiteSpace('\n ')(503,507) + PsiElement(SqlTokenType.KEYWORD)('AND')(507,510) + PsiWhiteSpace(' ')(510,511) + PsiElement(SqlTokenType.WORD)('e')(511,512) + PsiElement(SqlTokenType..)('.')(512,513) + PsiElement(SqlTokenType.WORD)('employee_id')(513,524) + PsiWhiteSpace(' ')(524,525) + PsiElement(SqlTokenType.OTHER)('=')(525,526) + PsiWhiteSpace(' ')(526,527) + SqlBlockCommentImpl(BLOCK_COMMENT)(527,552) + PsiElement(SqlTokenType./*)('/*')(527,529) + PsiWhiteSpace(' ')(529,530) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(530,549) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(530,538) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(530,538) + PsiElement(SqlTokenType..)('.')(538,539) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employeeId')(539,549) + PsiWhiteSpace(' ')(549,550) + PsiElement(SqlTokenType.*/)('*/')(550,552) + PsiElement(SqlTokenType.NUMBER)('0')(552,553) + PsiWhiteSpace('\n ')(553,557) + PsiElement(SqlTokenType.KEYWORD)('AND')(557,560) + PsiWhiteSpace(' ')(560,561) + PsiElement(SqlTokenType.WORD)('e')(561,562) + PsiElement(SqlTokenType..)('.')(562,563) + PsiElement(SqlTokenType.WORD)('join_date')(563,572) + PsiWhiteSpace(' ')(572,573) + PsiElement(SqlTokenType.<=)('<=')(573,575) + PsiWhiteSpace(' ')(575,576) + SqlBlockCommentImpl(BLOCK_COMMENT)(576,599) + PsiElement(SqlTokenType./*)('/*')(576,578) + PsiWhiteSpace(' ')(578,579) + SqlElAddExprImpl(EL_ADD_EXPR)(579,596) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(579,592) + PsiElement(SqlTokenType.EL_IDENTIFIER)('referenceDate')(579,592) + PsiWhiteSpace(' ')(592,593) + PsiElement(SqlTokenType.+)('+')(593,594) + PsiWhiteSpace(' ')(594,595) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(595,596) + PsiElement(SqlTokenType.EL_NUMBER)('1')(595,596) + PsiWhiteSpace(' ')(596,597) + PsiElement(SqlTokenType.*/)('*/')(597,599) + PsiElement(SqlTokenType.STRING)(''2099/12/31'')(599,611) + PsiWhiteSpace('\n ')(611,615) + SqlBlockCommentImpl(BLOCK_COMMENT)(615,658) + PsiElement(SqlTokenType./*)('/*')(615,617) + SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(617,655) + PsiElement(SqlTokenType.%if)('%if')(617,620) + PsiWhiteSpace(' ')(620,621) + SqlElFunctionCallExprImpl(EL_FUNCTION_CALL_EXPR)(621,655) + PsiElement(SqlTokenType.@)('@')(621,622) + PsiElement(SqlTokenType.EL_IDENTIFIER)('isNotBlank')(622,632) + SqlElParametersImpl(EL_PARAMETERS)(632,655) + PsiElement(SqlTokenType.()('(')(632,633) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(633,654) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(633,641) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(633,641) + PsiElement(SqlTokenType..)('.')(641,642) + PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(642,654) + PsiElement(SqlTokenType.))(')')(654,655) + PsiWhiteSpace(' ')(655,656) + PsiElement(SqlTokenType.*/)('*/')(656,658) + PsiWhiteSpace('\n ')(658,662) + SqlBlockCommentImpl(BLOCK_COMMENT)(662,710) + PsiElement(SqlTokenType./*)('/*')(662,664) + SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(664,707) + PsiElement(SqlTokenType.%if)('%if')(664,667) + PsiWhiteSpace(' ')(667,668) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(668,707) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(668,676) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(668,676) + PsiElement(SqlTokenType..)('.')(676,677) + PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(677,689) + PsiElement(SqlTokenType..)('.')(689,690) + PsiElement(SqlTokenType.EL_IDENTIFIER)('startsWith')(690,700) + SqlElParametersImpl(EL_PARAMETERS)(700,707) + PsiElement(SqlTokenType.()('(')(700,701) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(701,706) + PsiElement(SqlTokenType.EL_STRING)('"200"')(701,706) + PsiElement(SqlTokenType.))(')')(706,707) + PsiWhiteSpace(' ')(707,708) + PsiElement(SqlTokenType.*/)('*/')(708,710) + PsiElement(SqlTokenType.OTHER)('γ€€')(710,711) + PsiWhiteSpace('\n ')(711,715) + PsiElement(SqlTokenType.KEYWORD)('AND')(715,718) + PsiWhiteSpace(' ')(718,719) + PsiElement(SqlTokenType.WORD)('e')(719,720) + PsiElement(SqlTokenType..)('.')(720,721) + PsiElement(SqlTokenType.WORD)('department_id')(721,734) + PsiWhiteSpace(' ')(734,735) + PsiElement(SqlTokenType.OTHER)('=')(735,736) + PsiWhiteSpace(' ')(736,737) + SqlBlockCommentImpl(BLOCK_COMMENT)(737,769) + PsiElement(SqlTokenType./*)('/*')(737,739) + PsiWhiteSpace(' ')(739,740) + SqlElAddExprImpl(EL_ADD_EXPR)(740,766) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(740,761) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(740,748) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(740,748) + PsiElement(SqlTokenType..)('.')(748,749) + PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(749,761) + PsiWhiteSpace(' ')(761,762) + PsiElement(SqlTokenType.+)('+')(762,763) + PsiWhiteSpace(' ')(763,765) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(765,766) + PsiElement(SqlTokenType.EL_NUMBER)('1')(765,766) + PsiWhiteSpace(' ')(766,767) + PsiElement(SqlTokenType.*/)('*/')(767,769) + PsiElement(SqlTokenType.STRING)(''dept'')(769,775) + PsiWhiteSpace('\n ')(775,779) + SqlBlockCommentImpl(BLOCK_COMMENT)(779,822) + PsiElement(SqlTokenType./*)('/*')(779,781) + SqlElElseifDirectiveImpl(EL_ELSEIF_DIRECTIVE)(781,819) + PsiElement(SqlTokenType.%elseif)('%elseif')(781,788) + PsiWhiteSpace(' ')(788,789) + SqlElGeExprImpl(EL_GE_EXPR)(789,819) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(789,814) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(789,797) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(789,797) + PsiElement(SqlTokenType..)('.')(797,798) + PsiElement(SqlTokenType.EL_IDENTIFIER)('numberOfProjects')(798,814) + PsiWhiteSpace(' ')(814,815) + PsiElement(SqlTokenType.>=)('>=')(815,817) + PsiWhiteSpace(' ')(817,818) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(818,819) + PsiElement(SqlTokenType.EL_NUMBER)('3')(818,819) + PsiWhiteSpace(' ')(819,820) + PsiElement(SqlTokenType.*/)('*/')(820,822) + PsiWhiteSpace('\n ')(822,826) PsiElement(SqlTokenType.KEYWORD)('AND')(826,829) PsiWhiteSpace(' ')(829,830) PsiElement(SqlTokenType.WORD)('pe')(830,832) - PsiElement(SqlTokenType.OTHER)('.')(832,833) - PsiElement(SqlTokenType.WORD)('end_date')(833,841) - PsiWhiteSpace(' ')(841,842) - PsiElement(SqlTokenType.OTHER)('>')(842,843) - PsiElement(SqlTokenType.OTHER)('=')(843,844) - PsiWhiteSpace(' ')(844,845) - PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(845,857) - PsiWhiteSpace('\n ')(857,862) - SqlBlockCommentImpl(BLOCK_COMMENT)(862,870) - PsiElement(SqlTokenType./*)('/*')(862,864) - PsiElement(SqlTokenType.%end)('%end')(864,868) - PsiElement(SqlTokenType.*/)('*/')(868,870) - PsiWhiteSpace('\n ')(870,875) - SqlBlockCommentImpl(BLOCK_COMMENT)(875,914) - PsiElement(SqlTokenType./*)('/*')(875,877) - SqlElForDirectiveImpl(EL_FOR_DIRECTIVE)(877,911) - PsiElement(SqlTokenType.%for)('%for')(877,881) - PsiWhiteSpace(' ')(881,882) - PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(882,887) - PsiWhiteSpace(' ')(887,888) - PsiElement(SqlTokenType.:)(':')(888,889) - PsiWhiteSpace(' ')(889,890) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(890,911) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(890,898) - PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(890,898) - PsiElement(SqlTokenType..)('.')(898,899) - PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(899,911) - PsiWhiteSpace(' ')(911,912) - PsiElement(SqlTokenType.*/)('*/')(912,914) - PsiWhiteSpace('\n ')(914,922) - SqlBlockCommentImpl(BLOCK_COMMENT)(922,946) - PsiElement(SqlTokenType./*)('/*')(922,924) - SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(924,943) - PsiElement(SqlTokenType.%if)('%if')(924,927) - PsiWhiteSpace(' ')(927,929) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(929,943) - PsiElement(SqlTokenType.EL_IDENTIFIER)('child_has_next')(929,943) - PsiWhiteSpace(' ')(943,944) - PsiElement(SqlTokenType.*/)('*/')(944,946) - PsiWhiteSpace('\n ')(946,954) - PsiElement(SqlTokenType.KEYWORD)('AND')(954,957) - PsiWhiteSpace(' ')(957,958) - PsiElement(SqlTokenType.WORD)('pe')(958,960) - PsiElement(SqlTokenType.OTHER)('.')(960,961) - PsiElement(SqlTokenType.WORD)('parent_project')(961,975) - PsiWhiteSpace(' ')(975,976) - PsiElement(SqlTokenType.OTHER)('=')(976,977) - PsiWhiteSpace(' ')(977,978) - SqlBlockCommentImpl(BLOCK_COMMENT)(978,999) - PsiElement(SqlTokenType./*)('/*')(978,980) - PsiWhiteSpace(' ')(980,981) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(981,996) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(981,986) - PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(981,986) - PsiElement(SqlTokenType..)('.')(986,987) - PsiElement(SqlTokenType.EL_IDENTIFIER)('projectId')(987,996) - PsiWhiteSpace(' ')(996,997) - PsiElement(SqlTokenType.*/)('*/')(997,999) - PsiElement(SqlTokenType.NUMBER)('0')(999,1000) - PsiWhiteSpace('\n ')(1000,1007) - SqlBlockCommentImpl(BLOCK_COMMENT)(1007,1033) - PsiElement(SqlTokenType./*)('/*')(1007,1009) - SqlElForDirectiveImpl(EL_FOR_DIRECTIVE)(1009,1030) - PsiElement(SqlTokenType.%for)('%for')(1009,1013) - PsiWhiteSpace(' ')(1013,1014) - PsiElement(SqlTokenType.EL_IDENTIFIER)('p')(1014,1015) - PsiWhiteSpace(' ')(1015,1016) - PsiElement(SqlTokenType.:)(':')(1016,1017) - PsiWhiteSpace(' ')(1017,1018) - SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(1018,1030) - SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(1018,1023) - PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(1018,1023) - PsiElement(SqlTokenType..)('.')(1023,1024) - PsiElement(SqlTokenType.EL_IDENTIFIER)('member')(1024,1030) - PsiWhiteSpace(' ')(1030,1031) - PsiElement(SqlTokenType.*/)('*/')(1031,1033) - PsiWhiteSpace('\n ')(1033,1040) - PsiElement(SqlTokenType.KEYWORD)('AND')(1040,1043) - PsiWhiteSpace(' ')(1043,1044) - PsiElement(SqlTokenType.WORD)('pe')(1044,1046) - PsiElement(SqlTokenType.OTHER)('.')(1046,1047) - PsiElement(SqlTokenType.WORD)('type')(1047,1051) - PsiWhiteSpace(' ')(1051,1052) - PsiElement(SqlTokenType.OTHER)('=')(1052,1053) - PsiWhiteSpace(' ')(1053,1054) - SqlBlockCommentImpl(BLOCK_COMMENT)(1054,1104) - PsiElement(SqlTokenType./*)('/*')(1054,1056) - PsiWhiteSpace(' ')(1056,1057) - SqlElStaticFieldAccessExprImpl(EL_STATIC_FIELD_ACCESS_EXPR)(1057,1101) - PsiElement(SqlTokenType.@)('@')(1057,1058) - SqlElClassImpl(EL_CLASS)(1058,1083) - PsiElement(SqlTokenType.EL_IDENTIFIER)('example')(1058,1065) - PsiElement(SqlTokenType..)('.')(1065,1066) - PsiElement(SqlTokenType.EL_IDENTIFIER)('entity')(1066,1072) - PsiElement(SqlTokenType..)('.')(1072,1073) - PsiElement(SqlTokenType.EL_IDENTIFIER)('StaticType')(1073,1083) - PsiElement(SqlTokenType.@)('@')(1083,1084) - PsiElement(SqlTokenType.EL_IDENTIFIER)('PARAM1')(1084,1090) - PsiElement(SqlTokenType..)('.')(1090,1091) - PsiElement(SqlTokenType.EL_IDENTIFIER)('getValue')(1091,1099) - SqlElParametersImpl(EL_PARAMETERS)(1099,1101) - PsiElement(SqlTokenType.()('(')(1099,1100) - PsiElement(SqlTokenType.))(')')(1100,1101) - PsiWhiteSpace(' ')(1101,1102) - PsiElement(SqlTokenType.*/)('*/')(1102,1104) - PsiElement(SqlTokenType.STRING)(''0'')(1104,1107) - PsiWhiteSpace('\n ')(1107,1114) - SqlBlockCommentImpl(BLOCK_COMMENT)(1114,1123) - PsiElement(SqlTokenType./*)('/*')(1114,1116) - PsiElement(SqlTokenType.%end)('%end')(1116,1120) - PsiWhiteSpace(' ')(1120,1121) - PsiElement(SqlTokenType.*/)('*/')(1121,1123) - PsiWhiteSpace('\n ')(1123,1130) + PsiElement(SqlTokenType..)('.')(832,833) + PsiElement(SqlTokenType.WORD)('start_date')(833,843) + PsiWhiteSpace(' ')(843,844) + PsiElement(SqlTokenType.<=)('<=')(844,846) + PsiWhiteSpace(' ')(846,847) + PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(847,859) + PsiWhiteSpace('\n ')(859,863) + PsiElement(SqlTokenType.KEYWORD)('AND')(863,866) + PsiWhiteSpace(' ')(866,867) + PsiElement(SqlTokenType.WORD)('pe')(867,869) + PsiElement(SqlTokenType..)('.')(869,870) + PsiElement(SqlTokenType.WORD)('end_date')(870,878) + PsiWhiteSpace(' ')(878,879) + PsiElement(SqlTokenType.>=)('>=')(879,881) + PsiWhiteSpace(' ')(881,882) + PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(882,894) + PsiWhiteSpace('\n ')(894,898) + SqlBlockCommentImpl(BLOCK_COMMENT)(898,906) + PsiElement(SqlTokenType./*)('/*')(898,900) + PsiElement(SqlTokenType.%end)('%end')(900,904) + PsiElement(SqlTokenType.*/)('*/')(904,906) + PsiWhiteSpace('\n ')(906,910) + SqlBlockCommentImpl(BLOCK_COMMENT)(910,949) + PsiElement(SqlTokenType./*)('/*')(910,912) + SqlElForDirectiveImpl(EL_FOR_DIRECTIVE)(912,946) + PsiElement(SqlTokenType.%for)('%for')(912,916) + PsiWhiteSpace(' ')(916,917) + PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(917,922) + PsiWhiteSpace(' ')(922,923) + PsiElement(SqlTokenType.:)(':')(923,924) + PsiWhiteSpace(' ')(924,925) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(925,946) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(925,933) + PsiElement(SqlTokenType.EL_IDENTIFIER)('employee')(925,933) + PsiElement(SqlTokenType..)('.')(933,934) + PsiElement(SqlTokenType.EL_IDENTIFIER)('departmentId')(934,946) + PsiWhiteSpace(' ')(946,947) + PsiElement(SqlTokenType.*/)('*/')(947,949) + PsiWhiteSpace('\n ')(949,953) + SqlBlockCommentImpl(BLOCK_COMMENT)(953,977) + PsiElement(SqlTokenType./*)('/*')(953,955) + SqlElIfDirectiveImpl(EL_IF_DIRECTIVE)(955,974) + PsiElement(SqlTokenType.%if)('%if')(955,958) + PsiWhiteSpace(' ')(958,960) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(960,974) + PsiElement(SqlTokenType.EL_IDENTIFIER)('child_has_next')(960,974) + PsiWhiteSpace(' ')(974,975) + PsiElement(SqlTokenType.*/)('*/')(975,977) + PsiWhiteSpace('\n ')(977,981) + PsiElement(SqlTokenType.KEYWORD)('AND')(981,984) + PsiWhiteSpace(' ')(984,985) + PsiElement(SqlTokenType.WORD)('pe')(985,987) + PsiElement(SqlTokenType..)('.')(987,988) + PsiElement(SqlTokenType.WORD)('parent_project')(988,1002) + PsiWhiteSpace(' ')(1002,1003) + PsiElement(SqlTokenType.OTHER)('=')(1003,1004) + PsiWhiteSpace(' ')(1004,1005) + SqlBlockCommentImpl(BLOCK_COMMENT)(1005,1026) + PsiElement(SqlTokenType./*)('/*')(1005,1007) + PsiWhiteSpace(' ')(1007,1008) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(1008,1023) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(1008,1013) + PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(1008,1013) + PsiElement(SqlTokenType..)('.')(1013,1014) + PsiElement(SqlTokenType.EL_IDENTIFIER)('projectId')(1014,1023) + PsiWhiteSpace(' ')(1023,1024) + PsiElement(SqlTokenType.*/)('*/')(1024,1026) + PsiElement(SqlTokenType.NUMBER)('0')(1026,1027) + PsiWhiteSpace('\n ')(1027,1031) + SqlBlockCommentImpl(BLOCK_COMMENT)(1031,1057) + PsiElement(SqlTokenType./*)('/*')(1031,1033) + SqlElForDirectiveImpl(EL_FOR_DIRECTIVE)(1033,1054) + PsiElement(SqlTokenType.%for)('%for')(1033,1037) + PsiWhiteSpace(' ')(1037,1038) + PsiElement(SqlTokenType.EL_IDENTIFIER)('p')(1038,1039) + PsiWhiteSpace(' ')(1039,1040) + PsiElement(SqlTokenType.:)(':')(1040,1041) + PsiWhiteSpace(' ')(1041,1042) + SqlElFieldAccessExprImpl(EL_FIELD_ACCESS_EXPR)(1042,1054) + SqlElPrimaryExprImpl(EL_PRIMARY_EXPR)(1042,1047) + PsiElement(SqlTokenType.EL_IDENTIFIER)('child')(1042,1047) + PsiElement(SqlTokenType..)('.')(1047,1048) + PsiElement(SqlTokenType.EL_IDENTIFIER)('member')(1048,1054) + PsiWhiteSpace(' ')(1054,1055) + PsiElement(SqlTokenType.*/)('*/')(1055,1057) + PsiWhiteSpace('\n ')(1057,1061) + PsiElement(SqlTokenType.KEYWORD)('AND')(1061,1064) + PsiWhiteSpace(' ')(1064,1065) + PsiElement(SqlTokenType.WORD)('pe')(1065,1067) + PsiElement(SqlTokenType..)('.')(1067,1068) + PsiElement(SqlTokenType.WORD)('type')(1068,1072) + PsiWhiteSpace(' ')(1072,1073) + PsiElement(SqlTokenType.OTHER)('=')(1073,1074) + PsiWhiteSpace(' ')(1074,1075) + SqlBlockCommentImpl(BLOCK_COMMENT)(1075,1125) + PsiElement(SqlTokenType./*)('/*')(1075,1077) + PsiWhiteSpace(' ')(1077,1078) + SqlElStaticFieldAccessExprImpl(EL_STATIC_FIELD_ACCESS_EXPR)(1078,1122) + PsiElement(SqlTokenType.@)('@')(1078,1079) + SqlElClassImpl(EL_CLASS)(1079,1104) + PsiElement(SqlTokenType.EL_IDENTIFIER)('example')(1079,1086) + PsiElement(SqlTokenType..)('.')(1086,1087) + PsiElement(SqlTokenType.EL_IDENTIFIER)('entity')(1087,1093) + PsiElement(SqlTokenType..)('.')(1093,1094) + PsiElement(SqlTokenType.EL_IDENTIFIER)('StaticType')(1094,1104) + PsiElement(SqlTokenType.@)('@')(1104,1105) + PsiElement(SqlTokenType.EL_IDENTIFIER)('PARAM1')(1105,1111) + PsiElement(SqlTokenType..)('.')(1111,1112) + PsiElement(SqlTokenType.EL_IDENTIFIER)('getValue')(1112,1120) + SqlElParametersImpl(EL_PARAMETERS)(1120,1122) + PsiElement(SqlTokenType.()('(')(1120,1121) + PsiElement(SqlTokenType.))(')')(1121,1122) + PsiWhiteSpace(' ')(1122,1123) + PsiElement(SqlTokenType.*/)('*/')(1123,1125) + PsiElement(SqlTokenType.STRING)(''0'')(1125,1128) + PsiWhiteSpace('\n ')(1128,1130) SqlBlockCommentImpl(BLOCK_COMMENT)(1130,1139) PsiElement(SqlTokenType./*)('/*')(1130,1132) PsiElement(SqlTokenType.%end)('%end')(1132,1136) PsiWhiteSpace(' ')(1136,1137) PsiElement(SqlTokenType.*/)('*/')(1137,1139) - PsiWhiteSpace('\n ')(1139,1144) - SqlBlockCommentImpl(BLOCK_COMMENT)(1144,1153) - PsiElement(SqlTokenType./*)('/*')(1144,1146) - PsiElement(SqlTokenType.%end)('%end')(1146,1150) - PsiWhiteSpace(' ')(1150,1151) - PsiElement(SqlTokenType.*/)('*/')(1151,1153) - PsiWhiteSpace('\n ')(1153,1160) - SqlBlockCommentImpl(BLOCK_COMMENT)(1160,1169) - PsiElement(SqlTokenType./*)('/*')(1160,1162) - PsiElement(SqlTokenType.%end)('%end')(1162,1166) - PsiWhiteSpace(' ')(1166,1167) - PsiElement(SqlTokenType.*/)('*/')(1167,1169) - PsiWhiteSpace('\n')(1169,1170) - PsiElement(SqlTokenType.KEYWORD)('GROUP')(1170,1175) - PsiWhiteSpace(' ')(1175,1176) - PsiElement(SqlTokenType.KEYWORD)('BY')(1176,1178) - PsiWhiteSpace('\n ')(1178,1183) + PsiWhiteSpace('\n ')(1139,1141) + SqlBlockCommentImpl(BLOCK_COMMENT)(1141,1150) + PsiElement(SqlTokenType./*)('/*')(1141,1143) + PsiElement(SqlTokenType.%end)('%end')(1143,1147) + PsiWhiteSpace(' ')(1147,1148) + PsiElement(SqlTokenType.*/)('*/')(1148,1150) + PsiWhiteSpace('\n ')(1150,1152) + SqlBlockCommentImpl(BLOCK_COMMENT)(1152,1161) + PsiElement(SqlTokenType./*)('/*')(1152,1154) + PsiElement(SqlTokenType.%end)('%end')(1154,1158) + PsiWhiteSpace(' ')(1158,1159) + PsiElement(SqlTokenType.*/)('*/')(1159,1161) + PsiWhiteSpace('\n ')(1161,1163) + SqlBlockCommentImpl(BLOCK_COMMENT)(1163,1172) + PsiElement(SqlTokenType./*)('/*')(1163,1165) + PsiElement(SqlTokenType.%end)('%end')(1165,1169) + PsiWhiteSpace(' ')(1169,1170) + PsiElement(SqlTokenType.*/)('*/')(1170,1172) + PsiWhiteSpace('\n ')(1172,1174) + PsiElement(SqlTokenType.KEYWORD)('GROUP')(1174,1179) + PsiWhiteSpace(' ')(1179,1180) + PsiElement(SqlTokenType.KEYWORD)('BY')(1180,1182) + PsiWhiteSpace(' ')(1182,1183) PsiElement(SqlTokenType.WORD)('e')(1183,1184) - PsiElement(SqlTokenType.OTHER)('.')(1184,1185) + PsiElement(SqlTokenType..)('.')(1184,1185) PsiElement(SqlTokenType.WORD)('employee_id')(1185,1196) - PsiElement(SqlTokenType.OTHER)(',')(1196,1197) - PsiWhiteSpace('\n ')(1197,1202) - PsiElement(SqlTokenType.WORD)('u')(1202,1203) - PsiElement(SqlTokenType.OTHER)('.')(1203,1204) - PsiElement(SqlTokenType.WORD)('user_id')(1204,1211) - PsiElement(SqlTokenType.OTHER)(',')(1211,1212) - PsiWhiteSpace('\n ')(1212,1217) - PsiElement(SqlTokenType.WORD)('u')(1217,1218) - PsiElement(SqlTokenType.OTHER)('.')(1218,1219) - PsiElement(SqlTokenType.WORD)('user_name')(1219,1228) - PsiElement(SqlTokenType.OTHER)(',')(1228,1229) - PsiWhiteSpace('\n ')(1229,1234) - PsiElement(SqlTokenType.WORD)('u')(1234,1235) - PsiElement(SqlTokenType.OTHER)('.')(1235,1236) - PsiElement(SqlTokenType.WORD)('email')(1236,1241) - PsiElement(SqlTokenType.OTHER)(',')(1241,1242) - PsiWhiteSpace('\n ')(1242,1247) - PsiElement(SqlTokenType.WORD)('e')(1247,1248) - PsiElement(SqlTokenType.OTHER)('.')(1248,1249) - PsiElement(SqlTokenType.WORD)('department')(1249,1259) + PsiWhiteSpace('\n ')(1196,1204) + PsiElement(SqlTokenType.,)(',')(1204,1205) + PsiWhiteSpace(' ')(1205,1206) + PsiElement(SqlTokenType.WORD)('u')(1206,1207) + PsiElement(SqlTokenType..)('.')(1207,1208) + PsiElement(SqlTokenType.WORD)('user_id')(1208,1215) + PsiWhiteSpace('\n ')(1215,1223) + PsiElement(SqlTokenType.,)(',')(1223,1224) + PsiWhiteSpace(' ')(1224,1225) + PsiElement(SqlTokenType.WORD)('u')(1225,1226) + PsiElement(SqlTokenType..)('.')(1226,1227) + PsiElement(SqlTokenType.WORD)('user_name')(1227,1236) + PsiWhiteSpace('\n ')(1236,1244) + PsiElement(SqlTokenType.,)(',')(1244,1245) + PsiWhiteSpace(' ')(1245,1246) + PsiElement(SqlTokenType.WORD)('u')(1246,1247) + PsiElement(SqlTokenType..)('.')(1247,1248) + PsiElement(SqlTokenType.WORD)('email')(1248,1253) + PsiWhiteSpace('\n ')(1253,1261) + PsiElement(SqlTokenType.,)(',')(1261,1262) + PsiWhiteSpace(' ')(1262,1263) + PsiElement(SqlTokenType.WORD)('e')(1263,1264) + PsiElement(SqlTokenType..)('.')(1264,1265) + PsiElement(SqlTokenType.WORD)('department')(1265,1275)