Skip to content

Commit 0fed6a8

Browse files
Add diagnostic/formatting rule for optional semicolons
* Add configuration option `end_statement_with_semicolon` * Implement SemicolonAnalyzer and handle its results in FormatBuilder and CodeStyleChecker * Fix issue in LuaParser where certain statement nodes do not include the semicolon at the end * Add configuration option to template and documentation * Add unit test for new formatting rule
1 parent 44cd191 commit 0fed6a8

File tree

19 files changed

+392
-8
lines changed

19 files changed

+392
-8
lines changed

CodeFormatCore/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ target_sources(CodeFormatCore
3333
src/Format/Analyzer/FormatDocAnalyze.cpp
3434
src/Format/Analyzer/AlignAnalyzer.cpp
3535
src/Format/Analyzer/TokenAnalyzer.cpp
36+
src/Format/Analyzer/SemicolonAnalyzer.cpp
3637
src/Format/Analyzer/FormatResolve.cpp
3738
src/Format/Analyzer/SyntaxNodeHelper.cpp
3839
# rangeFormat
@@ -51,7 +52,7 @@ target_sources(CodeFormatCore
5152
src/Diagnostic/Spell/TextParser.cpp
5253
# diagnostic/codestyle
5354
src/Diagnostic/CodeStyle/CodeStyleChecker.cpp
54-
)
55+
)
5556

5657
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
5758
target_compile_options(CodeFormatCore PUBLIC /utf-8)

CodeFormatCore/include/CodeFormatCore/Config/LuaStyle.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,6 @@ class LuaStyle {
152152
bool leading_comma_style = false;
153153

154154
bool table_list_special_continue_indent = true;
155+
156+
EndStmtWithSemicolon end_statement_with_semicolon = EndStmtWithSemicolon::Keep;
155157
};

CodeFormatCore/include/CodeFormatCore/Config/LuaStyleEnum.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,11 @@ enum class AlignChainExpr {
8383
None,
8484
Always,
8585
OnlyCallStmt
86-
};
86+
};
87+
88+
enum class EndStmtWithSemicolon {
89+
Keep,
90+
Never,
91+
Always,
92+
SameLine
93+
};

CodeFormatCore/include/CodeFormatCore/Diagnostic/DiagnosticType.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum class DiagnosticType {
1111
StatementLineSpace,
1212
EndWithNewLine,
1313
Indent,
14+
Semicolon,
1415
NameStyle,
1516
Spell
1617
};

CodeFormatCore/include/CodeFormatCore/Format/Analyzer/FormatAnalyzerType.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ enum class FormatAnalyzerType {
77
AlignAnalyzer,
88
TokenAnalyzer,
99
FormatDocAnalyze,
10-
10+
SemicolonAnalyzer,
1111
Count,
1212
};
1313

CodeFormatCore/include/CodeFormatCore/Format/Analyzer/FormatStrategy.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ enum class TokenStrategy {
2424
StringDoubleQuote,
2525
TableSepSemicolon,
2626
TableSepComma,
27-
OriginRange
27+
OriginRange,
28+
StmtEndSemicolon,
29+
NewLineBeforeToken
2830
};
2931

3032
enum class TokenAddStrategy {
3133
None,
3234
TableAddColon,
33-
TableAddComma
35+
TableAddComma,
36+
StmtEndSemicolon
3437
};
3538

3639
enum class IndentStrategy {
@@ -120,3 +123,9 @@ struct AlignGroup {
120123
bool Resolve;
121124
std::size_t AlignPos;
122125
};
126+
127+
enum class SemicolonStrategy {
128+
Add,
129+
Remove,
130+
InsertNewLine
131+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include "FormatAnalyzer.h"
4+
#include "FormatStrategy.h"
5+
#include <unordered_map>
6+
#include <unordered_set>
7+
8+
class SemicolonAnalyzer : public FormatAnalyzer {
9+
public:
10+
DECLARE_FORMAT_ANALYZER(SemicolonAnalyzer)
11+
12+
SemicolonAnalyzer();
13+
14+
void Analyze(FormatState& f, const LuaSyntaxTree& t) override;
15+
16+
void Query(FormatState& f, LuaSyntaxNode syntaxNode, const LuaSyntaxTree& t, FormatResolve& resolve) override;
17+
18+
private:
19+
void AddSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t);
20+
void InsertNewLineBeforeNode(LuaSyntaxNode n, const LuaSyntaxTree& t);
21+
void RemoveSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t);
22+
bool IsFirstStmtOfLine(LuaSyntaxNode n, const LuaSyntaxTree& t);
23+
bool IsLastStmtOfLine(LuaSyntaxNode n, const LuaSyntaxTree& t);
24+
bool EndsWithSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t);
25+
LuaSyntaxNode GetLastNonCommentToken(LuaSyntaxNode n, const LuaSyntaxTree& t);
26+
27+
std::unordered_map<std::size_t, SemicolonStrategy> _semicolon;
28+
};

CodeFormatCore/src/Config/LuaStyle.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,16 @@ void LuaStyle::ParseFromMap(std::map<std::string, std::string, std::less<>> &con
268268
BOOL_OPTION(ignore_space_after_colon)
269269

270270
BOOL_OPTION(remove_call_expression_list_finish_comma)
271+
272+
if (configMap.count("end_statement_with_semicolon")) {
273+
if (configMap.at("end_statement_with_semicolon") == "keep") {
274+
end_statement_with_semicolon = EndStmtWithSemicolon::Keep;
275+
} else if (configMap.at("end_statement_with_semicolon") == "always") {
276+
end_statement_with_semicolon = EndStmtWithSemicolon::Always;
277+
} else if (configMap.at("end_statement_with_semicolon") == "never") {
278+
end_statement_with_semicolon = EndStmtWithSemicolon::Never;
279+
} else if (configMap.at("end_statement_with_semicolon") == "sameLine") {
280+
end_statement_with_semicolon = EndStmtWithSemicolon::SameLine;
281+
}
282+
}
271283
}

CodeFormatCore/src/Diagnostic/CodeStyle/CodeStyleChecker.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,36 @@ void CodeStyleChecker::BasicResolve(LuaSyntaxNode syntaxNode, const LuaSyntaxTre
9494

9595
break;
9696
}
97+
case TokenStrategy::StmtEndSemicolon: {
98+
switch (d.GetState().GetStyle().end_statement_with_semicolon) {
99+
case EndStmtWithSemicolon::Never:
100+
d.PushDiagnostic(DiagnosticType::Semicolon, textRange,
101+
LText("expected statement not to end with ;"));
102+
break;
103+
case EndStmtWithSemicolon::SameLine:
104+
d.PushDiagnostic(DiagnosticType::Semicolon, textRange,
105+
LText("; should only separate multiple statements on a single line"));
106+
break;
107+
default:
108+
break;
109+
}
110+
break;
111+
}
97112
default: {
98113
break;
99114
}
100115
}
101116

117+
switch (resolve.GetTokenAddStrategy()) {
118+
case TokenAddStrategy::StmtEndSemicolon:
119+
d.PushDiagnostic(DiagnosticType::Semicolon,
120+
TextRange(textRange.GetEndOffset(), 1),
121+
LText("expected ; at end of statement"));
122+
break;
123+
default:
124+
break;
125+
}
126+
102127
switch (resolve.GetNextSpaceStrategy()) {
103128
case NextSpaceStrategy::Space: {
104129
auto nextToken = syntaxNode.GetNextToken(t);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#include "CodeFormatCore/Format/Analyzer/SemicolonAnalyzer.h"
2+
#include "CodeFormatCore/Format/FormatState.h"
3+
#include "CodeFormatCore/Config/LuaStyleEnum.h"
4+
#include <LuaParser/Lexer/LuaTokenTypeDetail.h>
5+
6+
SemicolonAnalyzer::SemicolonAnalyzer() {
7+
}
8+
9+
void SemicolonAnalyzer::Analyze(FormatState& f, const LuaSyntaxTree& t) {
10+
if (f.GetStyle().end_statement_with_semicolon == EndStmtWithSemicolon::Keep) {
11+
return; // No analysis needed
12+
}
13+
14+
for (auto syntaxNode : t.GetSyntaxNodes()) {
15+
if (syntaxNode.IsNode(t)) {
16+
if (detail::multi_match::StatementMatch(syntaxNode.GetSyntaxKind(t))) {
17+
auto text = syntaxNode.GetText(t);
18+
switch (f.GetStyle().end_statement_with_semicolon) {
19+
case EndStmtWithSemicolon::Always:
20+
if (!EndsWithSemicolon(syntaxNode, t)) {
21+
AddSemicolon(syntaxNode, t);
22+
}
23+
break;
24+
case EndStmtWithSemicolon::Never:
25+
// is on same line as other statement -> needs to go on new line
26+
if (!IsFirstStmtOfLine(syntaxNode, t)) {
27+
InsertNewLineBeforeNode(syntaxNode, t);
28+
}
29+
if (EndsWithSemicolon(syntaxNode, t)) {
30+
RemoveSemicolon(syntaxNode, t);
31+
}
32+
break;
33+
case EndStmtWithSemicolon::SameLine:
34+
if (EndsWithSemicolon(syntaxNode, t)) {
35+
if (IsLastStmtOfLine(syntaxNode, t)) {
36+
RemoveSemicolon(syntaxNode, t);
37+
}
38+
}
39+
break;
40+
default:
41+
break;
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
void SemicolonAnalyzer::Query(FormatState& f, LuaSyntaxNode n, const LuaSyntaxTree& t, FormatResolve& resolve) {
49+
auto it = _semicolon.find(n.GetIndex());
50+
if (it != _semicolon.end()) {
51+
auto& strategy = it->second;
52+
switch (strategy) {
53+
case SemicolonStrategy::Add:
54+
resolve.SetTokenAddStrategy(TokenAddStrategy::StmtEndSemicolon);
55+
break;
56+
case SemicolonStrategy::Remove:
57+
resolve.SetTokenStrategy(TokenStrategy::StmtEndSemicolon);
58+
break;
59+
case SemicolonStrategy::InsertNewLine:
60+
resolve.SetTokenStrategy(TokenStrategy::NewLineBeforeToken);
61+
break;
62+
default:
63+
break;
64+
}
65+
}
66+
}
67+
68+
void SemicolonAnalyzer::AddSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t) {
69+
auto token = GetLastNonCommentToken(n, t);
70+
if (token.IsToken(t)) {
71+
_semicolon[token.GetIndex()] = SemicolonStrategy::Add;
72+
}
73+
}
74+
75+
void SemicolonAnalyzer::InsertNewLineBeforeNode(LuaSyntaxNode n, const LuaSyntaxTree& t) {
76+
auto token = n.GetFirstToken(t); // line breaks are put in front of the statement itself by non-first statements
77+
if (token.IsToken(t)) {
78+
_semicolon[token.GetIndex()] = SemicolonStrategy::InsertNewLine;
79+
}
80+
}
81+
82+
void SemicolonAnalyzer::RemoveSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t) {
83+
auto token = GetLastNonCommentToken(n, t);
84+
if (token.IsToken(t)) {
85+
_semicolon[token.GetIndex()] = SemicolonStrategy::Remove;
86+
}
87+
}
88+
89+
bool SemicolonAnalyzer::IsFirstStmtOfLine(LuaSyntaxNode n, const LuaSyntaxTree& t) {
90+
// check if current stmt starts on same line as the previous one ends
91+
auto startCurrent = n.GetStartLine(t);
92+
auto prev = n.GetPrevToken(t);
93+
if (!prev.IsNull(t)) {
94+
auto endPrev = prev.GetEndLine(t);
95+
return endPrev < startCurrent;
96+
}
97+
return true; // there's no previous token
98+
}
99+
100+
bool SemicolonAnalyzer::IsLastStmtOfLine(LuaSyntaxNode n, const LuaSyntaxTree& t) {
101+
// check if next stmt starts on same line as the current one ends
102+
auto currentEnd = n.GetEndLine(t);
103+
auto nextToken = n.GetNextToken(t);
104+
if (!nextToken.IsNull(t)) {
105+
auto nextStart = nextToken.GetStartLine(t);
106+
return currentEnd != nextStart;
107+
}
108+
return true; // there's no next token
109+
}
110+
111+
bool SemicolonAnalyzer::EndsWithSemicolon(LuaSyntaxNode n, const LuaSyntaxTree& t) {
112+
auto token = GetLastNonCommentToken(n, t);
113+
auto text = token.GetText(t);
114+
115+
return text == ";";
116+
}
117+
118+
LuaSyntaxNode SemicolonAnalyzer::GetLastNonCommentToken(LuaSyntaxNode n, const LuaSyntaxTree& t) {
119+
auto token = n.GetLastToken(t);
120+
switch (token.GetTokenKind(t)) {
121+
case TK_SHORT_COMMENT:
122+
case TK_LONG_COMMENT:
123+
case TK_SHEBANG: {
124+
return token.GetPrevToken(t);
125+
}
126+
default: {
127+
return token;
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)