diff --git a/src/main/java/org/domaframework/doma/intellij/Sql.bnf b/src/main/java/org/domaframework/doma/intellij/Sql.bnf index adb727fc..33c58ca0 100644 --- a/src/main/java/org/domaframework/doma/intellij/Sql.bnf +++ b/src/main/java/org/domaframework/doma/intellij/Sql.bnf @@ -94,7 +94,7 @@ private item ::= (comment | literal | word | BOOLEAN ) private comment ::= (block_comment | LINE_COMMENT) private literal ::= (STRING | NUMBER) -private word ::= (KEYWORD | WORD | DATATYPE) +private word ::= (KEYWORD | WORD | FUNCTION_NAME | DATATYPE) block_comment ::= "/*" (el_directive | BLOCK_COMMENT_CONTENT?) "*/" { pin=1 mixin="org.domaframework.doma.intellij.psi.SqlElCommentExprImpl" diff --git a/src/main/java/org/domaframework/doma/intellij/Sql.flex b/src/main/java/org/domaframework/doma/intellij/Sql.flex index 0983234c..b4dff5a5 100644 --- a/src/main/java/org/domaframework/doma/intellij/Sql.flex +++ b/src/main/java/org/domaframework/doma/intellij/Sql.flex @@ -7,7 +7,9 @@ import com.intellij.lexer.FlexLexer; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; +import org.domaframework.doma.intellij.SqlTokenHelper; +import org.domaframework.doma.intellij.psi.SqlTokenType; import org.domaframework.doma.intellij.psi.SqlTypes; %% @@ -19,132 +21,19 @@ import org.domaframework.doma.intellij.psi.SqlTypes; %function advance %type IElementType %{ - // SQL keywords - private static final Set KEYWORDS = Set.of( - "add", - "after", - "alter", - "all", - "and", - "as", - "asc", - "between", - "breadth", - "by", - "case", - "change", - "check", - "conflict", - "constraint", - "column", - "collate", - "comment", - "create", - "cross", - "cycle", - "database", - "default", - "delete", - "desc", - "distinct", - "do", - "drop", - "else", - "end", - "except", - "exists", - "first", - "foreign", - "from", - "full", - "group", - "having", - "if", - "in", - "index", - "inner", - "insert", - "intersect", - "into", - "is", - "join", - "key", - "lateral", - "left", - "like", - "limit", - "not", - "nothing", - "null", - "materialized", - "modify", - "offset", - "on", - "outer", - "or", - "order", - "primary", - "references", - "rename", - "returning", - "recursive", - "right", - "search", - "select", - "set", - "table", - "temporary", - "then", - "to", - "truncate", - "union", - "unique", - "update", - "using", - "values", - "view", - "when", - "where", - "with" - ); - - // 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) { - // TODO Reads plugin settings and allows users to register arbitrary keywords - return KEYWORDS.contains(word.toString().toLowerCase()); - } - - private static boolean isColumnDataType(CharSequence word) { - return DATATYPES.contains(word.toString().toLowerCase()); - } + private static boolean isKeyword(CharSequence word) { + // TODO Reads plugin settings and allows users to register arbitrary keywords + return SqlTokenHelper.getKeyword().contains(word.toString().toLowerCase()); + } + + private static boolean isColumnDataType(CharSequence word) { + return SqlTokenHelper.getDataTypeTokens().contains(word.toString().toLowerCase()); + } + + private static boolean isWindowFunction(CharSequence word) { + // TODO Reads plugin settings and allows users to register arbitrary function names + return SqlTokenHelper.getFunctionTokens().contains(word.toString().toLowerCase()); + } %} %eof{ return; @@ -181,7 +70,7 @@ 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 : isColumnDataType(yytext()) ? SqlTypes.DATATYPE : SqlTypes.WORD; } + {Word} { return isKeyword(yytext()) ? SqlTypes.KEYWORD : isColumnDataType(yytext()) ? SqlTypes.DATATYPE : isWindowFunction(yytext()) ? SqlTypes.FUNCTION_NAME : SqlTypes.WORD; } "." { return SqlTypes.DOT; } "," { return SqlTypes.COMMA; } "+" { return SqlTypes.PLUS;} diff --git a/src/main/java/org/domaframework/doma/intellij/SqlTokenHelper.java b/src/main/java/org/domaframework/doma/intellij/SqlTokenHelper.java new file mode 100644 index 00000000..e564e231 --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/SqlTokenHelper.java @@ -0,0 +1,61 @@ +/* + * 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; + +import java.util.HashSet; +import java.util.Set; +import org.domaframework.doma.intellij.tokens.MySqlFunctionToken; +import org.domaframework.doma.intellij.tokens.OracleFunctionToken; +import org.domaframework.doma.intellij.tokens.PostgresSqlFunctionToken; +import org.domaframework.doma.intellij.tokens.SqlDataTypeTokenUtil; +import org.domaframework.doma.intellij.tokens.SqlFunctionToken; +import org.domaframework.doma.intellij.tokens.SqlKeywordTokenUtil; + +public class SqlTokenHelper { + + static final Set KEYWORD_TOKENS = new HashSet<>(); + static final Set DATA_TYPE_TOKENS = new HashSet<>(); + static final Set FUNCTION_TOKENS = new HashSet<>(); + + static { + // Initialize keyword tokens + KEYWORD_TOKENS.addAll(SqlKeywordTokenUtil.getTokens()); + + // Initialize DataType tokens + DATA_TYPE_TOKENS.addAll(SqlDataTypeTokenUtil.getTokens()); + + // Initialize function tokens + FUNCTION_TOKENS.addAll(SqlFunctionToken.getTokens()); + FUNCTION_TOKENS.addAll(PostgresSqlFunctionToken.getTokens()); + FUNCTION_TOKENS.addAll(MySqlFunctionToken.getTokens()); + FUNCTION_TOKENS.addAll(OracleFunctionToken.getTokens()); + } + + // Keywords + public static Set getKeyword() { + return KEYWORD_TOKENS; + } + + // Data Types + public static Set getDataTypeTokens() { + return DATA_TYPE_TOKENS; + } + + // Functions + public static Set getFunctionTokens() { + return FUNCTION_TOKENS; + } +} 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 3eb1fbba..6f30775f 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//Function name", SqlSyntaxHighlighter.FUNCTION_NAME), new AttributesDescriptor("SQL//Syntax//DataType", SqlSyntaxHighlighter.DATATYPE), new AttributesDescriptor("SQL//Syntax//Other", SqlSyntaxHighlighter.OTHER), new AttributesDescriptor("SQL//Syntax//Word", SqlSyntaxHighlighter.WORD), @@ -106,59 +107,64 @@ public class SqlColorSettingsPage implements ColorSettingsPage { /** * Set highlights as you like */ - update product set /*%populate*/ - category = /* category */'category', - expected_sales = /* price * pieces */0, - unit_price = /* purchase / pieces */0, - /*%if mark != "XXX" */ - mark = /* mark */'mark', - /*%end */ - /*%! This comment delete */ - employee_type = ( - select /*%expand "e" */* from employee e - where - /*%for name : names */ - orderer = /* name */'orderer' - /*%if name.startWith("AA") && name.contains("_A") */ - AND div = 'A' - AND rank >= 5 - /*%elseif name.startWith("BB") || name.contains("_B") */ - AND div = 'B' - AND rank > 2 - AND rank < 5 - /*%else */ - AND div = 'C' - /*%end */ - /*%if name_has_next */ - /*# "OR" */ - /*%end */ - /*%end*/ - ) - where - type = /* @example.Type@Type.getValue() */'type' - and cost_limit <= /* cost + 100 */0 - and cost_limit >= /* cost - 100 */99999; + UPDATE product + SET /*%populate*/ + category = /* category */'category' + , expected_sales = /* price * pieces */0 + , unit_price = /* purchase / pieces */0 + /*%if mark != "XXX" */ + , mark = /* mark */'mark' + /*%end */ + /*%! This comment delete */ + , employee_type = ( SELECT /*%expand "e" */* + FROM employee e + WHERE + /*%for name : names */ + orderer = /* name */'orderer' + /*%if name.startWith("AA") && name.contains("_A") */ + AND div = 'A' + AND rank >= 5 + /*%elseif name.startWith("BB") || name.contains("_B") */ + AND div = 'B' + AND rank > 2 + AND rank < 5 + /*%else */ + AND div = 'C' + /*%end */ + /*%if name_has_next */ + /*# "OR" */ + /*%end */ + /*%end*/ ) + WHERE type = /* @example.Type@Type.getValue() */'type' + AND cost_limit <= /* cost + 100 */0 + AND cost_limit >= /* cost - 100 */99999; -- Demo Text2 - select p.project_id - , p.project_name - , p.project_type - , p.project_category - , p.pre_project - from project p - where p.project_type = /* new Project().type */'type' - and( - /*%for project : preProjects */ - /*%if project.category != null */ - project_category = /* project.category.plus('t') */'category' - /*%elseif project.pre == true */ - pre_project = /* project.preProjectId */0 - /*%if project_has_next */ - /*# "OR" */ - /*%end */ - /*%end */ - /*%end */ - ) + SELECT p.project_id + , p.project_name + , p.project_type + , p.project_category + , p.pre_project + FROM project p + WHERE p.project_type = /* new Project().type */'type' + AND ( + /*%for project : preProjects */ + /*%if project.category != null */ + project_category = /* project.category.plus('t') */'category' + /*%elseif project.pre == true */ + pre_project = /* project.preProjectId */0 + /*%if project_has_next */ + /*# "OR" */ + /*%end */ + /*%end */ + /*%end */) + + -- DemoText3 + SELECT common + , amount + , date + , SUM(amount) OVER(PARTITION BY common ORDER BY date) AS common_amount + FROM ammount_table """; } 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 0a57b961..8c2222af 100644 --- a/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java +++ b/src/main/java/org/domaframework/doma/intellij/highlighter/SqlSyntaxHighlighter.java @@ -38,6 +38,9 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { createTextAttributesKey("DOMA_SQL_WORD", DefaultLanguageHighlighterColors.IDENTIFIER); public static final TextAttributesKey DATATYPE = createTextAttributesKey("DOMA_SQL_DATATYPE", DefaultLanguageHighlighterColors.METADATA); + public static final TextAttributesKey FUNCTION_NAME = + createTextAttributesKey( + "DOMA_SQL_FUNCTION_NAME", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION); public static final TextAttributesKey STRING = createTextAttributesKey("DOMA_SQL_STRING", DefaultLanguageHighlighterColors.STRING); public static final TextAttributesKey NUMBER = @@ -141,6 +144,7 @@ public class SqlSyntaxHighlighter extends SyntaxHighlighterBase { static { map.put(SqlTypes.KEYWORD, KEYWORD); map.put(SqlTypes.DATATYPE, DATATYPE); + map.put(SqlTypes.FUNCTION_NAME, FUNCTION_NAME); map.put(SqlTypes.STRING, STRING); map.put(SqlTypes.OTHER, OTHER); map.put(SqlTypes.WORD, WORD); diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/MySqlFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/MySqlFunctionToken.java new file mode 100644 index 00000000..8d8fec01 --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/MySqlFunctionToken.java @@ -0,0 +1,200 @@ +/* + * 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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** MySQL specific functions */ +public class MySqlFunctionToken { + static final Set TOKENS = new HashSet<>(); + + static { + // Date and Time Functions + { + TOKENS.addAll( + Set.of( + "adddate", + "addtime", + "convert_tz", + "curdate", + "current_date", + "current_time", + "current_timestamp", + "curtime", + "date", + "date_add", + "date_format", + "date_sub", + "datediff", + "day", + "dayname", + "dayofmonth", + "dayofyear", + "extract", + "from_days", + "from_unixtime", + "get_format", + "hour", + "last_day", + "localtime", + "localtimestamp", + "makedate", + "maketime", + "microsecond", + "minute", + "month", + "monthname", + "now", + "period_add", + "period_diff", + "quarter", + "sec_to_time", + "second", + "str_to_date", + "subdate", + "subtime", + "sysdate", + "time", + "time_format", + "time_to_sec", + "timediff", + "timestamp", + "timestampadd", + "timestampdiff", + "to_days", + "to_seconds", + "unix_timestamp", + "unix_timestamp()", + "utc_date", + "utc_time", + "utc_timestamp", + "week", + "weekday", + "weekofyear", + "year", + "yearweek")); + } + // Mathematical Functions + { + TOKENS.addAll( + Set.of( + "abs", + "acos", + "asin", + "atan", + "atan2", + "ceil", + "ceiling", + "conv", + "cos", + "cot", + "crc32", + "degrees", + "div", + "exp", + "floor", + "ln", + "log10", + "log2", + "mod", + "pi", + "pow", + "power", + "radians", + "rand", + "sign", + "sin", + "sqrt", + "tan", + "truncate")); + } + // String Functions + { + TOKENS.addAll( + Set.of( + "ascii", + "bin", + "bit_length", + "char", + "char_length", + "character_length", + "concat", + "concat_ws", + "elt", + "export_set", + "field", + "find_in_set", + "format", + "from_base64", + "hex", + "insert", + "instr", + "lcase", + "left", + "length", + "like", + "load_file", + "locate", + "lower", + "lpad", + "ltrim", + "make_set", + "match", + "mid", + "not like", + "not regexp", + "oct", + "octet_length", + "ord", + "position", + "quote", + "regexp", + "regexp_instr", + "regexp_like", + "regexp_replace", + "regexp_substr", + "repeat", + "replace", + "reverse", + "right", + "rlike", + "rpad", + "rtrim", + "soundex", + "sounds like", + "space", + "strcmp", + "substr", + "substring", + "substring_index", + "to_base64", + "trim", + "ucase", + "unhex", + "upper", + "weight_string")); + } + // Convert Functions + { + TOKENS.addAll(Set.of("binary", "cast", "convert")); + } + } + + public static Set getTokens() { + return TOKENS; + } +} diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java new file mode 100644 index 00000000..f81408fd --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java @@ -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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** Oracle specific functions */ +public class OracleFunctionToken { + static final Set TOKENS = new HashSet<>(); + + static { + // Mathematical Functions + { + TOKENS.addAll(Set.of("abs", "ceil", "floor", "mod", "power", "sqrt", "sign", "trunc")); + } + // Conversion Functions + { + TOKENS.addAll( + Set.of( + "asciistr", + "instr", + "instrb", + "instr4", + "length", + "lengthb", + "length4", + "cast", + "numtodsinterval", + "numtoyminterval", + "to_char", + "to_date", + "to_number", + "unistr")); + } + // String Functions + { + TOKENS.addAll(Set.of("substr", "substrb", "substr4")); + } + // Lob Functions + { + TOKENS.addAll(Set.of("empty_blob", "empty_clob", "to_blob", "to_clob", "to_lob", "to_nclob")); + } + // Comparison functions + { + TOKENS.addAll(Set.of("decode", "greatest", "least", "coalesce", "nullif", "nvl")); + } + // Date/Time Functions + { + TOKENS.addAll( + Set.of( + "add_months", + "extract", + "months_between", + "round", + "sysdate", + "getdate", + "timestampadd", + "timestampdiff", + "to_date")); + } + } + + public static Set getTokens() { + return TOKENS; + } +} diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/PostgresSqlFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/PostgresSqlFunctionToken.java new file mode 100644 index 00000000..c7e9856b --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/PostgresSqlFunctionToken.java @@ -0,0 +1,130 @@ +/* + * 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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** PostgresSQL specific functions */ +public class PostgresSqlFunctionToken { + static final Set TOKENS = new HashSet<>(); + + static { + // Mathematical Functions + { + TOKENS.addAll( + Set.of( + "abs", + "cbrt", + "ceil", + "ceiling", + "degrees", + "div", + "erf", + "erfc", + "exp", + "factorial", + "floor", + "gcd", + "lcm", + "ln", + "log", + "log10", + "mod", + "pi", + "power", + "radians", + "round", + "scale", + "sign", + "sqrt", + "trim_scale", + "trunc", + "width_bucket")); + } + // Random Functions + { + TOKENS.addAll(Set.of("random", "random_normal", "setseed")); + } + // Trigonometric Functions + { + TOKENS.addAll( + Set.of( + "acos", "acosd", "asin", "asind", "atan", "atand", "atan2", "atan2d", "cos", "cosd", + "cot", "cotd", "sin", "sind", "tan", "tand", "sinh", "cosh", "tanh", "asinh", "acosh", + "atanh")); + } + // String Functions + { + TOKENS.addAll( + Set.of( + "bit_length", + "octet_length", + "length", + "substring", + "overlay", + "position", + "trim", + "get_bit", + "set_bit", + "get_byte", + "set_byte", + "convert", + "convert_from", + "convert_to", + "decode", + "encode")); + } + // Date/Time Functions + { + TOKENS.addAll( + Set.of( + "age", + "clock_timestamp", + "date_part", + "date_trunc", + "extract", + "isfinite", + "justify_days", + "justify_hours", + "justify_interval", + "make_date", + "make_interval", + "make_timestamp", + "make_time", + "make_timestamptz", + "now", + "statement_timestamp", + "timeofday", + "transaction_timestamp", + "to_char", + "to_date", + "to_timestamp", + "to_number", + "ntile", + "cume_dist", + "percent_rank", + "lead", + "first_value", + "last_value", + "nth_value")); + } + } + + public static Set getTokens() { + return TOKENS; + } +} diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/SqlDataTypeTokenUtil.java b/src/main/java/org/domaframework/doma/intellij/tokens/SqlDataTypeTokenUtil.java new file mode 100644 index 00000000..9902d1d4 --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/SqlDataTypeTokenUtil.java @@ -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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** SQL DATATYPE Token Provider */ +public class SqlDataTypeTokenUtil { + static final Set TOKENS = new HashSet<>(); + + static { + TOKENS.addAll( + 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")); + } + + public static Set getTokens() { + return TOKENS; + } +} diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java new file mode 100644 index 00000000..ed434b02 --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java @@ -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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** Default SQL Function Token */ +public class SqlFunctionToken { + static final Set TOKENS = new HashSet<>(); + + static { + TOKENS.addAll( + Set.of( + "coalesce", + "row_number", + "row_count", + "sum", + "avg", + "count", + "max", + "min", + "log", + "substring", + "trim", + "ltrim", + "rtlim", + "trim_array", + "btrim", + "replace", + "regexp_replace", + "over", + "rank", + "percent_rank", + "dense_rank", + "current_date", + "current_timestamp", + "now", + "mod")); + } + + public static Set getTokens() { + return TOKENS; + } +} diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java b/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java new file mode 100644 index 00000000..a005c18e --- /dev/null +++ b/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java @@ -0,0 +1,124 @@ +/* + * 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.tokens; + +import java.util.HashSet; +import java.util.Set; + +/** SQL Keyword Token Provider */ +public class SqlKeywordTokenUtil { + static final Set TOKENS = new HashSet<>(); + + static { + TOKENS.addAll( + Set.of( + "add", + "after", + "alter", + "all", + "and", + "as", + "asc", + "between", + "breadth", + "by", + "case", + "change", + "check", + "collate", + "column", + "comment", + "conflict", + "constraint", + "create", + "cross", + "cycle", + "database", + "default", + "delete", + "desc", + "distinct", + "do", + "drop", + "else", + "end", + "except", + "exists", + "first", + "following", + "foreign", + "from", + "full", + "group", + "having", + "if", + "in", + "index", + "inner", + "insert", + "intersect", + "into", + "is", + "join", + "key", + "lateral", + "left", + "like", + "limit", + "materialized", + "modify", + "not", + "nothing", + "null", + "offset", + "on", + "or", + "order", + "outer", + "partition", + "preceding", + "primary", + "range", + "recursive", + "references", + "rename", + "returning", + "right", + "row", + "rows", + "search", + "select", + "set", + "table", + "temporary", + "then", + "to", + "truncate", + "union", + "unique", + "update", + "using", + "values", + "view", + "when", + "where", + "with")); + } + + public static Set getTokens() { + return TOKENS; + } +} 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 index 70e37cd7..5e9978b8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -17,53 +17,24 @@ 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.FormattingMode 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.PsiWhiteSpace import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlBlockCommentBlock -import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock -import org.domaframework.doma.intellij.formatter.block.expr.SqlElBlockCommentBlock -import org.domaframework.doma.intellij.formatter.block.expr.SqlElConditionLoopCommentBlock -import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock -import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlock -import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateColumnAssignmentSymbolBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateSetGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithColumnGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQueryGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTypeParamBlock -import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock -import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock -import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock -import org.domaframework.doma.intellij.formatter.builder.SqlBlockBuilder import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder -import org.domaframework.doma.intellij.formatter.processor.SqlSetParentGroupProcessor -import org.domaframework.doma.intellij.formatter.util.CreateTableUtil import org.domaframework.doma.intellij.formatter.util.IndentType -import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext -import org.domaframework.doma.intellij.formatter.util.SqlBlockUtil import org.domaframework.doma.intellij.psi.SqlTypes open class SqlBlock( node: ASTNode, wrap: Wrap?, alignment: Alignment?, - private val customSpacingBuilder: SqlCustomSpacingBuilder?, - internal val spacingBuilder: SpacingBuilder, + internal open val spacingBuilder: SpacingBuilder, private val enableFormat: Boolean, private val formatMode: FormattingMode, ) : AbstractBlock( @@ -86,10 +57,27 @@ open class SqlBlock( var groupIndentLen: Int, ) - val blocks = mutableListOf() open var parentBlock: SqlBlock? = null open val childBlocks = mutableListOf() + fun getChildrenTextLen(): Int = + childBlocks.sumOf { child -> + val children = + child.childBlocks.filter { it !is SqlLineCommentBlock && it !is SqlBlockCommentBlock } + if (children.isNotEmpty()) { + child + .getChildrenTextLen() + .plus(child.getNodeText().length) + } else if (child.node.elementType == SqlTypes.DOT || + child.node.elementType == SqlTypes.RIGHT_PAREN + ) { + // Since elements do not include surrounding spaces, they should be excluded from the character count. + 0 + } else { + child.getNodeText().length.plus(1) + } + } + fun getChildBlocksDropLast( dropIndex: Int = 1, skipCommentBlock: Boolean = false, @@ -108,14 +96,6 @@ open class SqlBlock( 0, ) - private val blockBuilder = SqlBlockBuilder() - private val parentSetProcessor = SqlSetParentGroupProcessor(blockBuilder) - protected val blockUtil = SqlBlockUtil(this, isEnableFormat(), formatMode) - - protected open val pendingCommentBlocks = mutableListOf() - - fun isEnableFormat(): Boolean = enableFormat - open fun setParentGroupBlock(lastGroup: SqlBlock?) { parentBlock = lastGroup parentBlock?.addChildBlock(this) @@ -132,195 +112,10 @@ open class SqlBlock( fun getNodeText() = node.text.lowercase() - public override fun buildChildren(): MutableList { - if (isLeaf) return mutableListOf() - - var child = node.firstChildNode - var prevNonWhiteSpaceNode: ASTNode? = null - blockBuilder.addGroupTopNodeIndexHistory(this) - while (child != null) { - val lastBlock = blocks.lastOrNull() - val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory() - if (child !is PsiWhiteSpace) { - val childBlock = getBlock(child) - prevNonWhiteSpaceNode = child - updateCommentParentAndIdent(childBlock) - updateBlockParentAndLAddGroup(childBlock) - updateWhiteSpaceInclude(lastBlock, childBlock, lastGroup) - blocks.add(childBlock) - } else { - if (lastBlock !is SqlLineCommentBlock) { - blocks.add( - SqlWhitespaceBlock( - child, - parentBlock, - wrap, - alignment, - spacingBuilder, - ), - ) - } - } - child = child.treeNext - } - blocks.addAll(pendingCommentBlocks) - - return blocks - } - - /** - * Sets the parent and indentation for the comment element based on the element that was registered earlier. - */ - private fun updateCommentParentAndIdent(commentBlock: SqlBlock) { - if (commentBlock !is SqlCommentBlock) return - if (commentBlock is SqlElConditionLoopCommentBlock) { - blockBuilder.addConditionOrLoopBlock( - commentBlock, - ) - } else { - blockBuilder.addCommentBlock(commentBlock) - } - } - - /** - * Determines whether to retain the preceding newline (space) as a formatting target block based on the currently checked element. - */ - private fun updateWhiteSpaceInclude( - lastBlock: AbstractBlock?, - childBlock: SqlBlock, - lastGroup: SqlBlock?, - ) { - if (blocks.isNotEmpty() && lastBlock is SqlWhitespaceBlock) { - if (isSaveWhiteSpace(childBlock, lastGroup)) { - val whiteBlock = lastBlock as SqlBlock - whiteBlock.parentBlock = lastGroup - } else { - // Ignore space blocks for non-breaking elements - blocks.removeLast() - } - } - } + fun isEnableFormat(): Boolean = enableFormat open fun isSaveSpace(lastGroup: SqlBlock?): Boolean = false - /** - * Determines whether to retain the space (newline) based on the last registered group or the class of the currently checked element. - */ - private fun isSaveWhiteSpace( - childBlock: SqlBlock, - lastGroup: SqlBlock?, - ): Boolean = childBlock.isSaveSpace(lastGroup) - - /** - * Updates the parent block or registers itself as a new group block based on the class of the target block. - */ - private fun updateBlockParentAndLAddGroup(childBlock: SqlBlock) { - val lastGroupBlock = blockBuilder.getLastGroupTopNodeIndexHistory() - val lastIndentLevel = lastGroupBlock?.indent?.indentLevel - if (lastGroupBlock == null || lastIndentLevel == null) { - parentSetProcessor.updateGroupBlockAddGroup( - childBlock, - ) - return - } - - when (childBlock) { - is SqlKeywordGroupBlock -> { - parentSetProcessor.updateKeywordGroupBlockParentAndAddGroup( - lastGroupBlock, - lastIndentLevel, - childBlock, - ) - } - - is SqlColumnDefinitionRawGroupBlock -> { - parentSetProcessor.updateColumnDefinitionRawGroupBlockParentAndAddGroup( - lastGroupBlock, - lastIndentLevel, - childBlock, - ) - } - - is SqlColumnRawGroupBlock -> { - parentSetProcessor.updateColumnRawGroupBlockParentAndAddGroup( - lastGroupBlock, - childBlock, - ) - } - - is SqlInlineGroupBlock -> { - // case-end - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlInlineSecondGroupBlock -> { - parentSetProcessor.updateInlineSecondGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlColumnBlock -> { - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlElConditionLoopCommentBlock -> { - parentSetProcessor.updateConditionLoopCommentBlockParent( - lastGroupBlock, - childBlock, - ) - } - - is SqlWordBlock, is SqlOtherBlock, is SqlLineCommentBlock, is SqlBlockCommentBlock -> { - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlSubGroupBlock -> { - parentSetProcessor.updateSubGroupBlockParent( - childBlock, - ) - } - - is SqlRightPatternBlock -> { - parentSetProcessor.updateSqlRightPatternBlockParent( - childBlock, - ) - } - - is SqlElSymbolBlock -> { - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlDataTypeBlock -> { - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - is SqlCommaBlock -> { - if (lastGroupBlock is SqlCommaBlock) { - blockBuilder.removeLastGroupTopNodeIndexHistory() - } - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - - else -> { - parentSetProcessor.updateGroupBlockParentAndAddGroup( - childBlock, - ) - } - } - } - /** * Creates the indentation length for the block. */ @@ -328,150 +123,19 @@ open class SqlBlock( open fun createGroupIndentLen(): Int = 0 - /** - * Creates a block for the given child AST node. - */ - open fun getBlock(child: ASTNode): SqlBlock { - val defaultFormatCtx = - SqlBlockFormattingContext( - wrap, - alignment, - spacingBuilder, - isEnableFormat(), - formatMode, - ) - val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory() - return when (child.elementType) { - SqlTypes.KEYWORD -> { - return blockUtil.getKeywordBlock( - child, - blockBuilder.getLastGroupTopNodeIndexHistory(), - ) - } - - SqlTypes.DATATYPE -> { - SqlDataTypeBlock( - child, - defaultFormatCtx, - ) - } - - SqlTypes.LEFT_PAREN -> { - return blockUtil.getSubGroupBlock(lastGroup, child) - } - - SqlTypes.OTHER -> { - return if (lastGroup is SqlUpdateSetGroupBlock && - lastGroup.columnDefinitionGroupBlock != null - ) { - SqlUpdateColumnAssignmentSymbolBlock( - child, - defaultFormatCtx, - ) - } else { - SqlOtherBlock( - child, - defaultFormatCtx, - ) - } - } - - SqlTypes.RIGHT_PAREN -> return SqlRightPatternBlock( - child, - defaultFormatCtx, - ) - - SqlTypes.COMMA -> { - return if (lastGroup is SqlWithQueryGroupBlock) { - SqlWithCommonTableGroupBlock(child, defaultFormatCtx) - } else { - blockUtil.getCommaGroupBlock(lastGroup, child) - } - } - - SqlTypes.WORD -> { - return if (lastGroup is SqlWithQueryGroupBlock) { - SqlWithCommonTableGroupBlock(child, defaultFormatCtx) - } else { - blockUtil.getWordBlock(lastGroup, child) - } - } - - SqlTypes.BLOCK_COMMENT -> { - return if (lastGroup is SqlWithCommonTableGroupBlock) { - SqlWithCommonTableGroupBlock(child, defaultFormatCtx) - } else { - blockUtil.getBlockCommentBlock(child, createBlockCommentSpacingBuilder()) - } - } - - SqlTypes.LINE_COMMENT -> - return SqlLineCommentBlock( - child, - defaultFormatCtx, - ) - - SqlTypes.PLUS, SqlTypes.MINUS, SqlTypes.ASTERISK, SqlTypes.SLASH -> - return SqlElSymbolBlock( - child, - defaultFormatCtx, - ) - - SqlTypes.LE, SqlTypes.LT, SqlTypes.EL_EQ, SqlTypes.EL_NE, SqlTypes.GE, SqlTypes.GT -> - return SqlElSymbolBlock( - child, - defaultFormatCtx, - ) - - SqlTypes.STRING, SqlTypes.NUMBER, SqlTypes.BOOLEAN -> - return SqlLiteralBlock( - child, - defaultFormatCtx, - ) - - else -> - SqlUnknownBlock( - child, - defaultFormatCtx, - ) - } - } + open fun getBlock(child: ASTNode): SqlBlock = this /** * Creates a spacing builder for custom spacing rules. */ protected open fun createSpacingBuilder(): SqlCustomSpacingBuilder = SqlCustomSpacingBuilder() + override fun buildChildren(): List? = emptyList() + /** - * Creates a spacing builder specifically for block comments. + * Determines whether to adjust the indentation on pressing Enter. */ - 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), - ) + fun isAdjustIndentOnEnter(): Boolean = formatMode == FormattingMode.ADJUST_INDENT_ON_ENTER && !isEnableFormat() /** * Returns the indentation for the block. @@ -483,127 +147,10 @@ open class SqlBlock( Indent.getSpaceIndent(indent.indentLen) } - /** - * Determines whether to adjust the indentation on pressing Enter. - */ - fun isAdjustIndentOnEnter(): Boolean = formatMode == FormattingMode.ADJUST_INDENT_ON_ENTER && !isEnableFormat() - - /** - * Returns the spacing between two child blocks. - */ override fun getSpacing( child1: Block?, child2: Block, - ): Spacing? { - if (isAdjustIndentOnEnter()) return null - - // The end of a line comment element is a newline, so just add a space for the indent. - if (child1 is SqlLineCommentBlock && child2 is SqlBlock) { - return SqlCustomSpacingBuilder().getSpacing(child2) - } - - if (child2 is SqlWithColumnGroupBlock) { - return SqlCustomSpacingBuilder.normalSpacing - } - - if (child1 is SqlSubGroupBlock && child2 is SqlSubGroupBlock) { - return SqlCustomSpacingBuilder.nonSpacing - } - - // Do not leave a space after the comment block of the bind variable - if (child1 is SqlElBlockCommentBlock && child1 !is SqlElConditionLoopCommentBlock && child2 !is SqlCommentBlock) { - return SqlCustomSpacingBuilder.nonSpacing - } - - if (child2 is SqlElBlockCommentBlock) { - return when (child1) { - is SqlElBlockCommentBlock, is SqlWhitespaceBlock -> { - SqlCustomSpacingBuilder().getSpacing(child2) - } - - else -> SqlCustomSpacingBuilder.normalSpacing - } - } - - if (child1 is SqlFunctionParamBlock) { - return SqlCustomSpacingBuilder.nonSpacing - } - - if (child2 is SqlOtherBlock) { - return SqlCustomSpacingBuilder().getSpacing(child2) - } - - if (child1 is SqlWhitespaceBlock) { - when (child2) { - is SqlBlockCommentBlock, is SqlLineCommentBlock, is SqlNewGroupBlock -> { - return SqlCustomSpacingBuilder().getSpacing(child2) - } - } - } - - if (child2 is SqlNewGroupBlock) { - if (child1 is SqlSubGroupBlock && child2.indent.indentLevel == IndentType.ATTACHED) { - return SqlCustomSpacingBuilder.nonSpacing - } - when (child2) { - is SqlSubQueryGroupBlock -> { - if (child1 is SqlNewGroupBlock) { - return SqlCustomSpacingBuilder.normalSpacing - } - } - - is SqlDataTypeParamBlock, is SqlFunctionParamBlock -> return SqlCustomSpacingBuilder.nonSpacing - - else -> return SqlCustomSpacingBuilder.normalSpacing - } - } - - // Create Table Column Definition Raw Group Block - CreateTableUtil.getColumnDefinitionRawGroupSpacing(child1, child2)?.let { return it } - - when (child2) { - is SqlColumnDefinitionRawGroupBlock -> - SqlCustomSpacingBuilder() - .getSpacingColumnDefinitionRaw( - child2, - )?.let { return it } - - is SqlRightPatternBlock -> return SqlCustomSpacingBuilder().getSpacingRightPattern( - child2, - ) - - is SqlColumnBlock -> - SqlCustomSpacingBuilder() - .getSpacingColumnDefinition(child2) - ?.let { return it } - } - - if (child1 is SqlBlock && (child2 is SqlCommaBlock || child2 is SqlColumnRawGroupBlock)) { - SqlCustomSpacingBuilder().getSpacingWithIndentComma(child1, child2)?.let { return it } - } - - val spacing: Spacing? = customSpacingBuilder?.getCustomSpacing(child1, child2) - return spacing ?: spacingBuilder.getSpacing(this, child1, child2) - } - - /** - * Returns the child attributes for a new child at the specified index. - */ - 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) - } + ): Spacing? = null /** * Returns the child indentation for the block. 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 index 8505fde8..1931e5ca 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlCommaBlock.kt @@ -21,7 +21,6 @@ import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertColumnGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertValueGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock @@ -44,7 +43,6 @@ open class SqlCommaBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, @@ -61,6 +59,14 @@ open class SqlCommaBlock( SqlWithColumnGroupBlock::class, SqlKeywordGroupBlock::class, ) + + private val PARENT_INDENT_SYNC_TYPES = + listOf( + SqlUpdateColumnGroupBlock::class, + SqlInsertColumnGroupBlock::class, + SqlWithColumnGroupBlock::class, + SqlFunctionParamBlock::class, + ) } override val indent = @@ -92,14 +98,8 @@ open class SqlCommaBlock( return 0 } - val parentIndentSyncBlockTypes = - listOf( - SqlUpdateColumnGroupBlock::class, - SqlInsertColumnGroupBlock::class, - SqlWithColumnGroupBlock::class, - ) val parentIndentLen = parent.indent.groupIndentLen - if (TypeUtil.isExpectedClassType(parentIndentSyncBlockTypes, parent)) { + if (TypeUtil.isExpectedClassType(PARENT_INDENT_SYNC_TYPES, parent)) { return parentIndentLen } @@ -113,21 +113,22 @@ open class SqlCommaBlock( return parentIndentLen.plus(1) } - if (parent is SqlValuesParamGroupBlock) return 0 + val notNewLineTypes = + listOf( + SqlValuesParamGroupBlock::class, + SqlConditionalExpressionGroupBlock::class, + ) + if (TypeUtil.isExpectedClassType(notNewLineTypes, parent)) return 0 val grand = parent.parentBlock grand?.let { grand -> - if (grand is SqlCreateKeywordGroupBlock) { - val grandIndentLen = grand.indent.groupIndentLen - return grandIndentLen.plus(parentIndentLen).minus(1) - } - val grandIndent = grand.indent.indentLen val groupIndent = parentBlock?.indent?.groupIndentLen ?: 0 if (grand is SqlColumnRawGroupBlock) { return groupIndent.plus(grandIndent) } + return groupIndent.plus(grandIndent).minus(1) } return parentIndentLen.plus(1) 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 index 31898b95..ff5cd2e8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlDataTypeBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlDataTypeBlock.kt @@ -28,7 +28,6 @@ open class SqlDataTypeBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt new file mode 100644 index 00000000..3b3cef03 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -0,0 +1,564 @@ +/* + * 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.FormattingMode +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.PsiWhiteSpace +import com.intellij.psi.formatter.common.AbstractBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.expr.SqlElSymbolBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlock +import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateColumnAssignmentSymbolBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateSetGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithColumnGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTypeParamBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlEscapeBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock +import org.domaframework.doma.intellij.formatter.builder.SqlBlockBuilder +import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder +import org.domaframework.doma.intellij.formatter.processor.SqlSetParentGroupProcessor +import org.domaframework.doma.intellij.formatter.util.CreateTableUtil +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.formatter.util.SqlBlockUtil +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlFileBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val customSpacingBuilder: SqlCustomSpacingBuilder?, + override val spacingBuilder: SpacingBuilder, + enableFormat: Boolean, + private val formatMode: FormattingMode, +) : SqlBlock( + node, + wrap, + alignment, + spacingBuilder, + enableFormat, + formatMode, + ) { + private val blocks = mutableListOf() + + private val blockBuilder = SqlBlockBuilder() + private val parentSetProcessor = SqlSetParentGroupProcessor(blockBuilder) + private val blockUtil = SqlBlockUtil(this, isEnableFormat(), formatMode) + + private val pendingCommentBlocks = mutableListOf() + + public override fun buildChildren(): MutableList { + if (isLeaf) return mutableListOf() + + var child = node.firstChildNode + var prevNonWhiteSpaceNode: ASTNode? = null + blockBuilder.addGroupTopNodeIndexHistory(this) + while (child != null) { + val lastBlock = blocks.lastOrNull() + val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory() + if (child !is PsiWhiteSpace) { + val childBlock = getBlock(child) + prevNonWhiteSpaceNode = child + updateCommentParentAndIdent(childBlock) + updateBlockParentAndLAddGroup(childBlock) + updateWhiteSpaceInclude(lastBlock, childBlock, lastGroup) + blocks.add(childBlock) + } else { + if (lastBlock !is SqlLineCommentBlock) { + blocks.add( + SqlWhitespaceBlock( + child, + parentBlock, + wrap, + alignment, + spacingBuilder, + ), + ) + } + } + child = child.treeNext + } + blocks.addAll(pendingCommentBlocks) + + return blocks + } + + /** + * Creates a block for the given child AST node. + */ + override fun getBlock(child: ASTNode): SqlBlock { + val defaultFormatCtx = + SqlBlockFormattingContext( + wrap, + alignment, + spacingBuilder, + isEnableFormat(), + formatMode, + ) + val lastGroup = blockBuilder.getLastGroupTopNodeIndexHistory() + return when (child.elementType) { + SqlTypes.KEYWORD -> { + return blockUtil.getKeywordBlock( + child, + blockBuilder.getLastGroupTopNodeIndexHistory(), + ) + } + + SqlTypes.DATATYPE -> { + SqlDataTypeBlock( + child, + defaultFormatCtx, + ) + } + + SqlTypes.LEFT_PAREN -> { + return blockUtil.getSubGroupBlock(lastGroup, child) + } + + SqlTypes.OTHER -> { + return if (lastGroup is SqlUpdateSetGroupBlock && + lastGroup.columnDefinitionGroupBlock != null + ) { + SqlUpdateColumnAssignmentSymbolBlock( + child, + defaultFormatCtx, + ) + } else { + val escapeStrings = listOf("\"", "`", "[", "]") + if (escapeStrings.contains(child.text)) { + SqlEscapeBlock( + child, + defaultFormatCtx, + ) + } else { + SqlOtherBlock( + child, + defaultFormatCtx, + ) + } + } + } + + SqlTypes.RIGHT_PAREN -> return SqlRightPatternBlock( + child, + defaultFormatCtx, + ) + + SqlTypes.COMMA -> { + return if (lastGroup is SqlWithQueryGroupBlock) { + SqlWithCommonTableGroupBlock(child, defaultFormatCtx) + } else { + blockUtil.getCommaGroupBlock(lastGroup, child) + } + } + + SqlTypes.FUNCTION_NAME -> + return SqlFunctionGroupBlock(child, defaultFormatCtx) + + SqlTypes.WORD -> { + return if (lastGroup is SqlWithQueryGroupBlock) { + SqlWithCommonTableGroupBlock(child, defaultFormatCtx) + } else { + blockUtil.getWordBlock(lastGroup, child) + } + } + + SqlTypes.BLOCK_COMMENT -> { + return if (lastGroup is SqlWithCommonTableGroupBlock) { + SqlWithCommonTableGroupBlock(child, defaultFormatCtx) + } else { + blockUtil.getBlockCommentBlock(child, createBlockCommentSpacingBuilder()) + } + } + + SqlTypes.LINE_COMMENT -> + return SqlLineCommentBlock( + child, + defaultFormatCtx, + ) + + SqlTypes.PLUS, SqlTypes.MINUS, SqlTypes.ASTERISK, SqlTypes.SLASH -> + return SqlElSymbolBlock( + child, + defaultFormatCtx, + ) + + SqlTypes.LE, SqlTypes.LT, SqlTypes.EL_EQ, SqlTypes.EL_NE, SqlTypes.GE, SqlTypes.GT -> + return SqlElSymbolBlock( + child, + defaultFormatCtx, + ) + + SqlTypes.STRING, SqlTypes.NUMBER, SqlTypes.BOOLEAN -> + return SqlLiteralBlock( + child, + defaultFormatCtx, + ) + + else -> + SqlUnknownBlock( + child, + defaultFormatCtx, + ) + } + } + + /** + * Sets the parent and indentation for the comment element based on the element that was registered earlier. + */ + private fun updateCommentParentAndIdent(commentBlock: SqlBlock) { + if (commentBlock !is SqlCommentBlock) return + if (commentBlock is SqlElConditionLoopCommentBlock) { + blockBuilder.addConditionOrLoopBlock( + commentBlock, + ) + } else { + blockBuilder.addCommentBlock(commentBlock) + } + } + + /** + * Determines whether to retain the preceding newline (space) as a formatting target block based on the currently checked element. + */ + private fun updateWhiteSpaceInclude( + lastBlock: AbstractBlock?, + childBlock: SqlBlock, + lastGroup: SqlBlock?, + ) { + if (blocks.isNotEmpty() && lastBlock is SqlWhitespaceBlock) { + if (isSaveWhiteSpace(childBlock, lastGroup)) { + val whiteBlock = lastBlock as SqlBlock + whiteBlock.parentBlock = lastGroup + } else { + // Ignore space blocks for non-breaking elements + blocks.removeLast() + } + } + } + + /** + * Determines whether to retain the space (newline) based on the last registered group or the class of the currently checked element. + */ + private fun isSaveWhiteSpace( + childBlock: SqlBlock, + lastGroup: SqlBlock?, + ): Boolean = childBlock.isSaveSpace(lastGroup) + + /** + * Updates the parent block or registers itself as a new group block based on the class of the target block. + */ + private fun updateBlockParentAndLAddGroup(childBlock: SqlBlock) { + val lastGroupBlock = blockBuilder.getLastGroupTopNodeIndexHistory() + val lastIndentLevel = lastGroupBlock?.indent?.indentLevel + if (lastGroupBlock == null || lastIndentLevel == null) { + parentSetProcessor.updateGroupBlockAddGroup( + childBlock, + ) + return + } + + when (childBlock) { + is SqlKeywordGroupBlock -> { + parentSetProcessor.updateKeywordGroupBlockParentAndAddGroup( + lastGroupBlock, + lastIndentLevel, + childBlock, + ) + } + + is SqlColumnDefinitionRawGroupBlock -> { + parentSetProcessor.updateColumnDefinitionRawGroupBlockParentAndAddGroup( + lastGroupBlock, + lastIndentLevel, + childBlock, + ) + } + + is SqlColumnRawGroupBlock -> { + parentSetProcessor.updateColumnRawGroupBlockParentAndAddGroup( + lastGroupBlock, + childBlock, + ) + } + + is SqlInlineGroupBlock -> { + // case-end + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlInlineSecondGroupBlock -> { + parentSetProcessor.updateInlineSecondGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlColumnBlock -> { + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlElConditionLoopCommentBlock -> { + parentSetProcessor.updateConditionLoopCommentBlockParent( + lastGroupBlock, + childBlock, + ) + } + + is SqlWordBlock, is SqlOtherBlock, is SqlLineCommentBlock, is SqlBlockCommentBlock -> { + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlSubGroupBlock -> { + parentSetProcessor.updateSubGroupBlockParent( + lastGroupBlock, + childBlock, + ) + } + + is SqlRightPatternBlock -> { + parentSetProcessor.updateSqlRightPatternBlockParent( + childBlock, + ) + } + + is SqlElSymbolBlock -> { + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlDataTypeBlock -> { + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + is SqlCommaBlock -> { + if (lastGroupBlock is SqlCommaBlock) { + blockBuilder.removeLastGroupTopNodeIndexHistory() + } + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + + else -> { + parentSetProcessor.updateGroupBlockParentAndAddGroup( + childBlock, + ) + } + } + } + + /** + * Creates a spacing builder specifically for block comments. + */ + private 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), + ) + + /** + * Returns the spacing between two child blocks. + */ + override fun getSpacing( + child1: Block?, + child2: Block, + ): Spacing? { + if (isAdjustIndentOnEnter()) return null + val childBlock1: SqlBlock? = child1 as? SqlBlock + val childBlock2: SqlBlock = child2 as SqlBlock + + // The end of a line comment element is a newline, so just add a space for the indent. + if (childBlock1 is SqlLineCommentBlock) { + return SqlCustomSpacingBuilder().getSpacing(childBlock2) + } + + if (childBlock2 is SqlWithColumnGroupBlock) { + return SqlCustomSpacingBuilder.normalSpacing + } + + if (childBlock1 is SqlSubGroupBlock && childBlock2 is SqlSubGroupBlock) { + return SqlCustomSpacingBuilder.nonSpacing + } + + // Do not leave a space after the comment block of the bind variable + if (childBlock1 is SqlElBlockCommentBlock && childBlock1 !is SqlElConditionLoopCommentBlock && childBlock2 !is SqlCommentBlock) { + return SqlCustomSpacingBuilder.nonSpacing + } + + if (childBlock2 is SqlElBlockCommentBlock) { + return when (childBlock1) { + is SqlElBlockCommentBlock, is SqlWhitespaceBlock -> { + SqlCustomSpacingBuilder().getSpacing(childBlock2) + } + + else -> SqlCustomSpacingBuilder.normalSpacing + } + } + + if (childBlock1?.node?.elementType == SqlTypes.DOT || + childBlock2.node.elementType == SqlTypes.DOT + ) { + return SqlCustomSpacingBuilder.nonSpacing + } + + if (childBlock1 is SqlEscapeBlock) { + return if (!childBlock1.isEndEscape) { + SqlCustomSpacingBuilder.nonSpacing + } else { + SqlCustomSpacingBuilder.normalSpacing + } + } + + if (childBlock2 is SqlEscapeBlock) { + if (childBlock2.isEndEscape) { + return SqlCustomSpacingBuilder.nonSpacing + } + return SqlCustomSpacingBuilder().getSpacing(childBlock2) + } + + if (childBlock2 is SqlOtherBlock) { + return SqlCustomSpacingBuilder().getSpacing(childBlock2) + } + + if (childBlock1 is SqlWhitespaceBlock) { + when (childBlock2) { + is SqlBlockCommentBlock, is SqlLineCommentBlock, is SqlNewGroupBlock -> { + return SqlCustomSpacingBuilder().getSpacing(childBlock2) + } + } + } + + if (childBlock2 is SqlNewGroupBlock) { + if (childBlock1 is SqlSubGroupBlock && childBlock2.indent.indentLevel == IndentType.ATTACHED) { + return SqlCustomSpacingBuilder.nonSpacing + } + when (childBlock2) { + is SqlSubQueryGroupBlock -> { + if (childBlock1 is SqlNewGroupBlock) { + return SqlCustomSpacingBuilder.normalSpacing + } + } + + is SqlDataTypeParamBlock, is SqlFunctionParamBlock -> return SqlCustomSpacingBuilder.nonSpacing + + else -> return SqlCustomSpacingBuilder.normalSpacing + } + } + + // Create Table Column Definition Raw Group Block + CreateTableUtil.getColumnDefinitionRawGroupSpacing(childBlock1, childBlock2)?.let { return it } + + when (childBlock2) { + is SqlColumnDefinitionRawGroupBlock -> + SqlCustomSpacingBuilder() + .getSpacingColumnDefinitionRaw( + childBlock2, + )?.let { return it } + + is SqlRightPatternBlock -> return SqlCustomSpacingBuilder().getSpacingRightPattern( + childBlock2, + ) + + is SqlColumnBlock -> + SqlCustomSpacingBuilder() + .getSpacingColumnDefinition(childBlock2) + ?.let { return it } + } + + if (childBlock1 is SqlBlock && (childBlock2 is SqlCommaBlock || childBlock2 is SqlColumnRawGroupBlock)) { + SqlCustomSpacingBuilder().getSpacingWithIndentComma(childBlock1, childBlock2)?.let { return it } + } + + val spacing: Spacing? = customSpacingBuilder?.getCustomSpacing(childBlock1, childBlock2) + return spacing ?: spacingBuilder.getSpacing(this, childBlock1, childBlock2) + } + + override fun isLeaf(): Boolean = false + + /** + * Returns the child attributes for a new child at the specified index. + */ + 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) + } +} 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 index 1b73c78e..a89d0b42 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLiteralBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlLiteralBlock.kt @@ -27,7 +27,6 @@ open class SqlLiteralBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index 9c404412..2a2443ba 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt @@ -45,7 +45,6 @@ open class SqlRightPatternBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index 0836dc9b..348cff5b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlUnknownBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlUnknownBlock.kt @@ -27,7 +27,6 @@ class SqlUnknownBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index 83d9a977..e8bf38ee 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWhitespaceBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlWhitespaceBlock.kt @@ -34,7 +34,6 @@ class SqlWhitespaceBlock( node, wrap, alignment, - null, spacingBuilder, false, FormattingMode.ADJUST_INDENT_ON_ENTER, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt index 8764ae11..f3833c4a 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlCommentBlock.kt @@ -29,7 +29,6 @@ abstract class SqlCommentBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index 47a56628..60747a52 100644 --- 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 @@ -66,7 +66,6 @@ class SqlElFieldAccessBlock( child, context.wrap, context.alignment, - customSpacingBuilder, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index c931eaf0..7ad2ddfc 100644 --- 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 @@ -58,7 +58,6 @@ class SqlElFunctionCallBlock( child, wrap, alignment, - createSpacingBuilder(), spacingBuilder, context.enableFormat, context.formatMode, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlExprBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlExprBlock.kt index 89f76817..d52a5c89 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlExprBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/expr/SqlExprBlock.kt @@ -28,7 +28,6 @@ abstract class SqlExprBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, 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 index 32eaf2b9..bff78b51 100644 --- 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 @@ -27,7 +27,6 @@ abstract class SqlNewGroupBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/column/SqlRawGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/column/SqlRawGroupBlock.kt index 82de996c..d3eeb505 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/column/SqlRawGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/column/SqlRawGroupBlock.kt @@ -30,7 +30,6 @@ abstract class SqlRawGroupBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt index adb194ad..c8506243 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt @@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -54,7 +55,7 @@ open class SqlSecondKeywordBlock( lastGroup?.let { last -> val prevKeyword = last.childBlocks.findLast { it is SqlKeywordBlock } prevKeyword?.let { prev -> - return !SqlKeywordUtil.isSetLineKeyword(getNodeText(), prev.getNodeText()) + return !SqlKeywordUtil.isSetLineKeyword(getNodeText(), prev.getNodeText()) && last !is SqlFunctionParamBlock } return !SqlKeywordUtil.isSetLineKeyword(getNodeText(), last.getNodeText()) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/update/SqlUpdateColumnAssignmentSymbolBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/update/SqlUpdateColumnAssignmentSymbolBlock.kt index 25f5afee..d1b7479a 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/update/SqlUpdateColumnAssignmentSymbolBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/update/SqlUpdateColumnAssignmentSymbolBlock.kt @@ -17,7 +17,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.update import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlOtherBlock +import org.domaframework.doma.intellij.formatter.block.other.SqlOtherBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext /** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt index e6deefa0..669acc0d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlFunctionParamBlock.kt @@ -17,6 +17,9 @@ package org.domaframework.doma.intellij.formatter.block.group.subgroup import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext import org.domaframework.doma.intellij.psi.SqlTypes @@ -38,21 +41,59 @@ class SqlFunctionParamBlock( override fun setParentGroupBlock(lastGroup: SqlBlock?) { super.setParentGroupBlock(lastGroup) indent.indentLevel = IndentType.PARAM - indent.indentLen = 0 + indent.indentLen = createBlockIndentLen() indent.groupIndentLen = createGroupIndentLen() } + override fun setParentPropertyBlock(lastGroup: SqlBlock?) { + (lastGroup as? SqlFunctionGroupBlock)?.parameterGroupBlock = this + } + override fun createGroupIndentLen(): Int { - prevChildren?.let { prevList -> - return prevList - .dropLast(1) - .sumOf { it.getNodeText().length.plus(1) } + val parentFunctionName = parentBlock as? SqlFunctionGroupBlock + parentFunctionName?.let { parent -> + return parent.indent.groupIndentLen .plus(getNodeText().length) - .minus(prevList.count { it.node.elementType == SqlTypes.DOT } * 2) - .plus(1) } - return getNodeText().length + + val prevChildrenDropLast = + prevChildren?.dropLast(1)?.filter { + it !is SqlLineCommentBlock && + it !is SqlBlockCommentBlock && + it.node.elementType != SqlTypes.DOT + } + ?: emptyList() + val prevLength = + prevChildrenDropLast + .sumOf { it.getNodeText().length } + .plus(getNodeText().length) + return prevLength.plus(prevChildrenDropLast.count()).plus(1) } - override fun createBlockIndentLen(): Int = 0 + override fun createBlockIndentLen(): Int { + parentBlock?.let { parent -> + if (parent !is SqlSubGroupBlock) { + return if (parent is SqlFunctionGroupBlock) { + parent.indent.groupIndentLen + } else { + parent.indent.groupIndentLen.plus(1) + } + } + + val children = + prevChildren?.dropLast(1)?.filter { + it !is SqlLineCommentBlock && + it !is SqlBlockCommentBlock && + it.node != SqlTypes.DOT + } + children?.let { prevList -> + return prevList + .sumOf { it.getNodeText().length.plus(1) } + .plus(parent.indent.groupIndentLen) + .plus(getNodeText().length) + .plus(1) + } + } + return 0 + } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlParallelListBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlParallelListBlock.kt index 192f018a..c381a1eb 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlParallelListBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlParallelListBlock.kt @@ -39,17 +39,5 @@ class SqlParallelListBlock( 0, ) - override fun createBlockIndentLen(): Int { - parentBlock?.let { parent -> - return parent - .getChildBlocksDropLast(skipCommentBlock = true) - .sumOf { it.getNodeText().length.plus(1) } - .plus(parent.indent.indentLen) - .plus(parent.getNodeText().length) - .plus(1) - } - return 0 - } - - override fun createGroupIndentLen(): Int = indent.indentLen.plus(1) + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt index 1edc80c0..17aabee0 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt @@ -18,6 +18,9 @@ package org.domaframework.doma.intellij.formatter.block.group.subgroup 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.formatter.block.comment.SqlBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlJoinGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock @@ -51,8 +54,19 @@ open class SqlSubQueryGroupBlock( parent.indent.indentLen } else if (parent is SqlWithQuerySubGroupBlock) { parent.indent.groupIndentLen - } else { + } else if (parent is SqlJoinGroupBlock) { parent.indent.groupIndentLen.plus(1) + } else { + val children = prevChildren?.filter { it !is SqlLineCommentBlock && it !is SqlBlockCommentBlock } + return children + ?.dropLast(1) + ?.sumOf { prev -> + prev + .getChildrenTextLen() + .plus(prev.getNodeText().length.plus(1)) + }?.plus(parent.indent.groupIndentLen) + ?.plus(1) + ?: offset } } ?: offset @@ -65,6 +79,6 @@ open class SqlSubQueryGroupBlock( return indent.indentLen } } - return indent.indentLen.plus(1) + return indent.indentLen.plus(getNodeText().length) } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.kt new file mode 100644 index 00000000..1aba59ec --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlEscapeBlock.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.other + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlEscapeBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlOtherBlock(node, context) { + var isEndEscape = false + + override fun setParentPropertyBlock(lastGroup: SqlBlock?) { + super.setParentPropertyBlock(lastGroup) + // If the number of escape characters, including itself, is even + isEndEscape = lastGroup?.childBlocks?.count { it is SqlEscapeBlock }?.let { it % 2 == 0 } == true + } + + override fun createBlockIndentLen(): Int = + if (isEndEscape) { + 0 + } else { + 1 + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt similarity index 92% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt index c9a28a3c..aad9a994 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlOtherBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/other/SqlOtherBlock.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block +package org.domaframework.doma.intellij.formatter.block.other 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.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -27,7 +28,6 @@ open class SqlOtherBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt new file mode 100644 index 00000000..91fe6cbb --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.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.word + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlBlockCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlLineCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlFunctionGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlWordBlock(node, context) { + var parameterGroupBlock: SqlFunctionParamBlock? = null + var prevChildren: List = emptyList() + + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + prevChildren = lastGroup?.childBlocks?.toList() ?: emptyList() + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun createBlockIndentLen(): Int { + parentBlock?.let { parent -> + val children = prevChildren.dropLast(1).filter { it !is SqlLineCommentBlock && it !is SqlBlockCommentBlock } + val prevBlocksLength = + children + .sumOf { prev -> + prev + .getChildrenTextLen() + .plus( + if (prev.node.elementType == SqlTypes.DOT || + prev.node.elementType == SqlTypes.RIGHT_PAREN + ) { + 0 + } else { + prev.getNodeText().length.plus(1) + }, + ) + }.plus(parent.indent.groupIndentLen) + return if (parent is SqlSubGroupBlock) { + // parent.indent.groupIndentLen + prevBlocksLength + } else { + prevBlocksLength.plus(1) + } + } + return 0 + } + + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlWordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlWordBlock.kt index 32c749bc..e9af5595 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlWordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlWordBlock.kt @@ -30,7 +30,6 @@ open class SqlWordBlock( node, context.wrap, context.alignment, - null, context.spacingBuilder, context.enableFormat, context.formatMode, @@ -57,11 +56,11 @@ open class SqlWordBlock( override fun buildChildren(): MutableList = mutableListOf() override fun createBlockIndentLen(): Int { - parentBlock?.let { - when (it) { + parentBlock?.let { parent -> + when (parent) { is SqlSubQueryGroupBlock -> { - val parentIndentLen = it.indent.groupIndentLen - val grand = it.parentBlock + val parentIndentLen = parent.indent.groupIndentLen + val grand = parent.parentBlock if (grand != null && grand.getNodeText().lowercase() == "create") { val grandIndentLen = grand.indent.groupIndentLen return grandIndentLen.plus(parentIndentLen).plus(1) @@ -70,8 +69,7 @@ open class SqlWordBlock( } else -> { - val parentLen = it.getNodeText().length - return it.indent.groupIndentLen.plus(parentLen.plus(1)) + return parent.indent.groupIndentLen.plus(1) } } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt index cc9f7230..479f9c9f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlFormattingModelBuilder.kt @@ -25,7 +25,7 @@ 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.formatter.block.SqlFileBlock import org.domaframework.doma.intellij.psi.SqlTypes import org.domaframework.doma.intellij.setting.SqlLanguage import org.domaframework.doma.intellij.setting.state.DomaToolsFormatEnableSettings @@ -47,7 +47,7 @@ class SqlFormattingModelBuilder : FormattingModelBuilder { return FormattingModelProvider .createFormattingModelForPsiFile( formattingContext.containingFile, - SqlBlock( + SqlFileBlock( formattingContext.node, Wrap.createWrap(WrapType.NONE, false), Alignment.createAlignment(), @@ -82,18 +82,30 @@ class SqlFormattingModelBuilder : FormattingModelBuilder { .spacing(1, 1, 0, false, 0) .before(SqlTypes.RIGHT_PAREN) .spacing(0, 0, 0, false, 0) + .after(SqlTypes.RIGHT_PAREN) + .spacing(1, 1, 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) - .before(SqlTypes.LEFT_PAREN) + .around(SqlTypes.SLASH) + .spacing(1, 1, 0, false, 0) + .before(SqlTypes.FUNCTION_NAME) .spacing(1, 1, 0, false, 0) private fun createCustomSpacingBuilder(): SqlCustomSpacingBuilder = SqlCustomSpacingBuilder() .withSpacing( + SqlTypes.FUNCTION_NAME, + SqlTypes.LEFT_PAREN, + SqlCustomSpacingBuilder.nonSpacing, + ).withSpacing( + SqlTypes.FUNCTION_NAME, + SqlTypes.OTHER, + SqlCustomSpacingBuilder.normalSpacing, + ).withSpacing( SqlTypes.NUMBER, SqlTypes.COMMA, SqlCustomSpacingBuilder.nonSpacing, @@ -128,5 +140,9 @@ class SqlFormattingModelBuilder : FormattingModelBuilder { SqlTypes.OTHER, SqlTypes.OTHER, SqlCustomSpacingBuilder.normalSpacing, + ).withSpacing( + SqlTypes.OTHER, + SqlTypes.WORD, + SqlCustomSpacingBuilder.normalSpacing, ) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlSetParentGroupProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlSetParentGroupProcessor.kt index 9de1aa61..77d622e0 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlSetParentGroupProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlSetParentGroupProcessor.kt @@ -35,6 +35,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlU import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.builder.SqlBlockBuilder import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil @@ -345,7 +346,22 @@ class SqlSetParentGroupProcessor( } } - fun updateSubGroupBlockParent(childBlock: SqlSubGroupBlock) { + fun updateSubGroupBlockParent( + lastGroupBlock: SqlBlock, + childBlock: SqlSubGroupBlock, + ) { + val prevBlock = lastGroupBlock.childBlocks.lastOrNull() + if (prevBlock is SqlFunctionGroupBlock) { + setParentGroups( + SetParentContext( + childBlock, + blockBuilder, + ), + ) { history -> + return@setParentGroups prevBlock + } + return + } updateGroupBlockParentAndAddGroup(childBlock) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockUtil.kt index 9ef80fa1..6835cae8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockUtil.kt @@ -62,6 +62,7 @@ import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlDataTyp import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock import org.domaframework.doma.intellij.formatter.builder.SqlCustomSpacingBuilder @@ -278,7 +279,9 @@ class SqlBlockUtil( child, sqlBlockFormattingCtx, )?.let { return it } - + if (lastGroupBlock is SqlFunctionGroupBlock) { + return SqlKeywordBlock(child, IndentType.NONE, sqlBlockFormattingCtx) + } SqlSecondKeywordBlock( child, sqlBlockFormattingCtx, diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt index df5d9239..84aa3f27 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt @@ -312,6 +312,7 @@ class SqlKeywordUtil { "constraint" to setOf("on"), "update" to setOf("do"), "set" to setOf("by", "cycle"), + "order" to setOf("partition", "by"), ) fun isSetLineKeyword( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/SqlFormatVisitor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/SqlFormatVisitor.kt index 767c4392..8fbbde0e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/SqlFormatVisitor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/visitor/SqlFormatVisitor.kt @@ -43,7 +43,7 @@ class SqlFormatVisitor : PsiRecursiveElementVisitor() { if (PsiTreeUtil.getParentOfType(element, SqlBlockComment::class.java) == null) { when (element.elementType) { - SqlTypes.KEYWORD, SqlTypes.COMMA, SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN, SqlTypes.WORD -> { + SqlTypes.KEYWORD, SqlTypes.COMMA, SqlTypes.LEFT_PAREN, SqlTypes.RIGHT_PAREN, SqlTypes.WORD, SqlTypes.FUNCTION_NAME -> { replaces.add(element) } diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index 5720d9aa..9044d055 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -54,6 +54,10 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("Select.sql", "Select$formatDataPrefix.sql") } + fun testSelectEscapeFunctionNameFormatter() { + formatSqlFile("SelectEscapeFunctionName.sql", "SelectEscapeFunctionName$formatDataPrefix.sql") + } + fun testSelectCaseEndFormatter() { formatSqlFile("SelectCaseEnd.sql", "SelectCaseEnd$formatDataPrefix.sql") } diff --git a/src/test/testData/sql/formatter/SelectCaseEnd.sql b/src/test/testData/sql/formatter/SelectCaseEnd.sql index eb186684..59e7823b 100644 --- a/src/test/testData/sql/formatter/SelectCaseEnd.sql +++ b/src/test/testData/sql/formatter/SelectCaseEnd.sql @@ -1,4 +1,4 @@ Select case when div = 'A' then 'AAA' -when div = 'B' then 'BBB' -else 'CCC' end as divName + when div = 'B' then 'BBB' + else 'CCC' end as divName from users \ No newline at end of file diff --git a/src/test/testData/sql/formatter/SelectEscapeFunctionName.sql b/src/test/testData/sql/formatter/SelectEscapeFunctionName.sql new file mode 100644 index 00000000..7afe617f --- /dev/null +++ b/src/test/testData/sql/formatter/SelectEscapeFunctionName.sql @@ -0,0 +1,6 @@ +DELETE FROM x " div " + WHERE id IN ( SELECT [count.cnt ] - 1 + , id + FROM x2 + WHERE id > 101 + AND `div`. "age$a" = 't' ) \ No newline at end of file diff --git a/src/test/testData/sql/formatter/SelectEscapeFunctionName_format.sql b/src/test/testData/sql/formatter/SelectEscapeFunctionName_format.sql new file mode 100644 index 00000000..f16c526a --- /dev/null +++ b/src/test/testData/sql/formatter/SelectEscapeFunctionName_format.sql @@ -0,0 +1,6 @@ +DELETE FROM x "div" + WHERE id IN ( SELECT [count.cnt] - 1 + , id + FROM x2 + WHERE id > 101 + AND `div`."age$a" = 't' ) diff --git a/src/test/testData/sql/parser/SQLParser.txt b/src/test/testData/sql/parser/SQLParser.txt index 6f1bf234..a03818e7 100644 --- a/src/test/testData/sql/parser/SQLParser.txt +++ b/src/test/testData/sql/parser/SQLParser.txt @@ -32,7 +32,7 @@ SQL File(0,1310) PsiWhiteSpace('\n ')(217,225) PsiElement(SqlTokenType.,)(',')(225,226) PsiWhiteSpace(' ')(226,227) - PsiElement(SqlTokenType.WORD)('COUNT')(227,232) + PsiElement(SqlTokenType.FUNCTION_NAME)('COUNT')(227,232) PsiElement(SqlTokenType.()('(')(232,233) PsiElement(SqlTokenType.WORD)('pe')(233,235) PsiElement(SqlTokenType..)('.')(235,236) @@ -292,7 +292,7 @@ SQL File(0,1310) PsiWhiteSpace(' ')(878,879) PsiElement(SqlTokenType.<=)('<=')(879,881) PsiWhiteSpace(' ')(881,882) - PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(882,894) + PsiElement(SqlTokenType.FUNCTION_NAME)('CURRENT_DATE')(882,894) PsiWhiteSpace('\n ')(894,898) PsiElement(SqlTokenType.KEYWORD)('AND')(898,901) PsiWhiteSpace(' ')(901,902) @@ -302,7 +302,7 @@ SQL File(0,1310) PsiWhiteSpace(' ')(913,914) PsiElement(SqlTokenType.>=)('>=')(914,916) PsiWhiteSpace(' ')(916,917) - PsiElement(SqlTokenType.WORD)('CURRENT_DATE')(917,929) + PsiElement(SqlTokenType.FUNCTION_NAME)('CURRENT_DATE')(917,929) PsiWhiteSpace('\n ')(929,933) SqlBlockCommentImpl(BLOCK_COMMENT)(933,941) PsiElement(SqlTokenType./*)('/*')(933,935)