Skip to content

Commit df03d03

Browse files
jnmugerwaRongrong Zhong
authored andcommitted
Add parser linting support
1 parent 07c62c2 commit df03d03

File tree

14 files changed

+575
-6
lines changed

14 files changed

+575
-6
lines changed

parser/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
<maven.compiler.target>1.6</maven.compiler.target>
1515
</properties>
1616
<dependencies>
17+
<dependency>
18+
<groupId>com.google.guava</groupId>
19+
<artifactId>guava</artifactId>
20+
<version>11.0.2</version>
21+
</dependency>
1722
<dependency>
1823
<groupId>junit</groupId>
1924
<artifactId>junit</artifactId>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.lint;
16+
17+
import com.facebook.coresql.parser.AstNode;
18+
import com.facebook.coresql.parser.SqlParserDefaultVisitor;
19+
import com.facebook.coresql.warning.WarningCode;
20+
import com.facebook.coresql.warning.WarningCollector;
21+
22+
public abstract class LintingVisitor
23+
extends SqlParserDefaultVisitor
24+
{
25+
private final WarningCollector warningCollector;
26+
27+
public LintingVisitor(WarningCollector collector)
28+
{
29+
this.warningCollector = collector;
30+
}
31+
32+
public void addWarningToCollector(WarningCode code, String warningMessage, AstNode node)
33+
{
34+
warningCollector.add(code, warningMessage, node);
35+
}
36+
37+
/**
38+
* Entry point to recursive visiting routine. We recurse, add any warnings to the current collector, then return.
39+
*
40+
* @param node The root of the AST we're validating
41+
*/
42+
public void lint(AstNode node)
43+
{
44+
node.jjtAccept(this, null);
45+
}
46+
47+
public WarningCollector getWarningCollector()
48+
{
49+
return warningCollector;
50+
}
51+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.lint;
16+
17+
import com.facebook.coresql.parser.AndExpression;
18+
import com.facebook.coresql.parser.OrExpression;
19+
import com.facebook.coresql.warning.WarningCollector;
20+
21+
import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTANDEXPRESSION;
22+
import static com.facebook.coresql.parser.SqlParserTreeConstants.JJTOREXPRESSION;
23+
import static com.facebook.coresql.warning.StandardWarningCode.MIXING_AND_OR_WITHOUT_PARENTHESES;
24+
25+
/**
26+
* A visitor that validates an AST built from an SQL string. Right now, it validates
27+
* a single clause: don't mix AND and OR without parentheses.
28+
*/
29+
public class MixedAndOr
30+
extends LintingVisitor
31+
{
32+
private static final String WARNING_MESSAGE = "Mixing AND and OR without parentheses.";
33+
34+
public MixedAndOr(WarningCollector collector)
35+
{
36+
super(collector);
37+
}
38+
39+
@Override
40+
public void visit(OrExpression node, Void data)
41+
{
42+
if (node.jjtGetParent().getId() == JJTANDEXPRESSION) {
43+
super.addWarningToCollector(MIXING_AND_OR_WITHOUT_PARENTHESES.getWarningCode(),
44+
WARNING_MESSAGE,
45+
node);
46+
}
47+
defaultVisit(node, data);
48+
}
49+
50+
@Override
51+
public void visit(AndExpression node, Void data)
52+
{
53+
if (node.jjtGetParent().getId() == JJTOREXPRESSION) {
54+
super.addWarningToCollector(MIXING_AND_OR_WITHOUT_PARENTHESES.getWarningCode(),
55+
WARNING_MESSAGE,
56+
node);
57+
}
58+
defaultVisit(node, data);
59+
}
60+
}

parser/src/main/java/com/facebook/coresql/parser/AstNode.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ public final AstNode GetFirstChildOfKind(int kind)
7575
return null;
7676
}
7777

78-
public String GetCoordinates()
78+
public Location getLocation()
7979
{
80-
return beginToken.beginLine + ":" + beginToken.beginColumn + "-" + endToken.endLine + ":" + endToken.endColumn;
80+
return new Location(beginToken.beginLine, beginToken.beginColumn, endToken.endLine, endToken.endColumn);
8181
}
8282

8383
@Override
8484
public String toString(String prefix)
8585
{
86-
return super.toString(prefix) + " (" + GetCoordinates() + ")" +
86+
return super.toString(prefix) + " (" + getLocation().toString() + ")" +
8787
(NumChildren() == 0 ? " (" + beginToken.image + ")" : "");
8888
}
8989
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.parser;
16+
17+
import static java.lang.String.format;
18+
19+
public class Location
20+
{
21+
private final int beginLine;
22+
private final int beginColumn;
23+
private final int endLine;
24+
private final int endColumn;
25+
26+
public Location(int beginLine, int beginColumn, int endLine, int endColumn)
27+
{
28+
this.beginLine = beginLine;
29+
this.beginColumn = beginColumn;
30+
this.endLine = endLine;
31+
this.endColumn = endColumn;
32+
}
33+
34+
@Override
35+
public String toString()
36+
{
37+
return format("%d:%d-%d:%d", beginLine, beginColumn, endLine, endColumn);
38+
}
39+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.warning;
16+
17+
import com.facebook.coresql.parser.Location;
18+
19+
import static java.lang.String.format;
20+
import static java.util.Objects.requireNonNull;
21+
22+
public final class CoreSqlWarning
23+
{
24+
private final WarningCode warningCode;
25+
private final String message;
26+
private final Location location;
27+
28+
public CoreSqlWarning(WarningCode warningCode, String message,
29+
int beginLine,
30+
int beginColumn,
31+
int endLine,
32+
int endColumn)
33+
{
34+
this.warningCode = requireNonNull(warningCode, "Warning code is null");
35+
this.message = requireNonNull(message, "Warning message is null");
36+
this.location = new Location(beginLine, beginColumn, endLine, endColumn);
37+
}
38+
39+
public WarningCode getWarningCode()
40+
{
41+
return warningCode;
42+
}
43+
44+
@Override
45+
public String toString()
46+
{
47+
return format("Warning@%s message: %s [%s]", location, message, warningCode);
48+
}
49+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.warning;
16+
17+
import com.facebook.coresql.parser.AstNode;
18+
import com.google.common.collect.ImmutableList;
19+
import com.google.common.collect.LinkedListMultimap;
20+
import com.google.common.collect.Multimap;
21+
22+
import java.util.List;
23+
24+
import static java.util.Objects.requireNonNull;
25+
26+
public class DefaultWarningCollector
27+
implements WarningCollector
28+
{
29+
private final Multimap<WarningCode, CoreSqlWarning> warnings = LinkedListMultimap.create();
30+
private final WarningCollectorConfig config;
31+
32+
public DefaultWarningCollector(WarningCollectorConfig config)
33+
{
34+
this.config = requireNonNull(config, "config is null");
35+
}
36+
37+
@Override
38+
public void add(WarningCode code, String warningMessage, AstNode node)
39+
{
40+
requireNonNull(code, "warning code is null");
41+
requireNonNull(warningMessage, "warning message is null");
42+
requireNonNull(node, "node is null");
43+
addWarningIfNumWarningsLessThanConfig(new CoreSqlWarning(code,
44+
warningMessage,
45+
node.beginToken.beginLine,
46+
node.beginToken.beginColumn,
47+
node.beginToken.endLine,
48+
node.beginToken.endColumn));
49+
}
50+
51+
@Override
52+
public List<CoreSqlWarning> getAllWarnings()
53+
{
54+
return ImmutableList.copyOf(warnings.values());
55+
}
56+
57+
@Override
58+
public void clearWarnings()
59+
{
60+
warnings.clear();
61+
}
62+
63+
private void addWarningIfNumWarningsLessThanConfig(CoreSqlWarning coreSqlWarning)
64+
{
65+
if (warnings.size() < config.getMaxWarnings()) {
66+
warnings.put(coreSqlWarning.getWarningCode(), coreSqlWarning);
67+
}
68+
}
69+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.facebook.coresql.warning;
16+
17+
public enum StandardWarningCode
18+
{
19+
MIXING_AND_OR_WITHOUT_PARENTHESES(0x0000_0001);
20+
21+
private final WarningCode warningCode;
22+
23+
StandardWarningCode(int code)
24+
{
25+
warningCode = new WarningCode(code, name());
26+
}
27+
28+
public WarningCode getWarningCode()
29+
{
30+
return warningCode;
31+
}
32+
}

0 commit comments

Comments
 (0)