Skip to content

Commit ebe6eb0

Browse files
authored
Merge pull request #124 from AlWoSp/awa/semicolon-rule-pr
Add diagnostic/formatting rule for optional semicolons
2 parents 44cd191 + 3f6eab0 commit ebe6eb0

File tree

19 files changed

+411
-7
lines changed

19 files changed

+411
-7
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") == "same_line") {
280+
end_statement_with_semicolon = EndStmtWithSemicolon::SameLine;
281+
}
282+
}
271283
}

CodeFormatCore/src/Diagnostic/CodeStyle/CodeStyleChecker.cpp

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

0 commit comments

Comments
 (0)