Skip to content
This repository has been archived by the owner on Aug 17, 2020. It is now read-only.

Commit

Permalink
Question marks in a query don't match number of arguments lint check (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
geralt-encore authored and JakeWharton committed Dec 18, 2017
1 parent 7abcba4 commit 9ba06a9
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 0 deletions.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ buildscript {
'minSdk': 14,
'compileSdk': 27,
'kotlin': '1.1.60',
'lint': '26.0.1'
]

repositories {
Expand All @@ -21,6 +22,7 @@ allprojects {
repositories {
mavenCentral()
google()
jcenter()
}

group = GROUP
Expand Down Expand Up @@ -50,6 +52,11 @@ ext {
rxBinding = 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
junit = 'junit:junit:4.12'
truth = 'com.google.truth:truth:0.36'

// Lint dependencies.
lintApi = "com.android.tools.lint:lint-api:${versions.lint}"
lint = "com.android.tools.lint:lint:${versions.lint}"
lintTests = "com.android.tools.lint:lint-tests:${versions.lint}"
}

configurations {
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include ':sqlbrite'
include ':sqlbrite-kotlin'
include ':sqlbrite-lint'
include ':sample'

rootProject.name = 'sqlbrite-root'
16 changes: 16 additions & 0 deletions sqlbrite-lint/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apply plugin: 'kotlin'

dependencies {
compileOnly rootProject.ext.kotlinStdLib
compileOnly rootProject.ext.lintApi

testImplementation rootProject.ext.junit
testImplementation rootProject.ext.lint
testImplementation rootProject.ext.lintTests
}

jar {
manifest {
attributes("Lint-Registry-v2": "com.squareup.sqlbrite3.BriteIssueRegistry")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2017 Square, Inc.
*
* 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
*
* http://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 com.squareup.sqlbrite3

import com.android.tools.lint.client.api.IssueRegistry

class BriteIssueRegistry : IssueRegistry() {

override fun getIssues() = listOf(SqlBriteArgCountDetector.ISSUE)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2017 Square, Inc.
*
* 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
*
* http://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 com.squareup.sqlbrite3

import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.ConstantEvaluator.evaluateString
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope.JAVA_FILE
import com.android.tools.lint.detector.api.Scope.TEST_SOURCES
import com.android.tools.lint.detector.api.Severity
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import java.util.EnumSet

private const val BRITE_DATABASE = "com.squareup.sqlbrite3.BriteDatabase"
private const val QUERY_METHOD_NAME = "query"
private const val CREATE_QUERY_METHOD_NAME = "createQuery"

class SqlBriteArgCountDetector : Detector(), Detector.UastScanner {

companion object {

val ISSUE: Issue = Issue.create(
"SqlBriteArgCount",
"Number of provided arguments doesn't match number " +
"of arguments specified in query",
"When providing arguments to query you need to provide the same amount of " +
"arguments that is specified in query.",
Category.MESSAGES,
9,
Severity.ERROR,
Implementation(SqlBriteArgCountDetector::class.java, EnumSet.of(JAVA_FILE, TEST_SOURCES)))
}

override fun getApplicableMethodNames() = listOf(CREATE_QUERY_METHOD_NAME, QUERY_METHOD_NAME)

override fun visitMethod(context: JavaContext, call: UCallExpression, method: PsiMethod) {
val evaluator = context.evaluator

if (evaluator.isMemberInClass(method, BRITE_DATABASE)) {
// Skip non varargs overloads.
if (!method.isVarArgs) return

// Position of sql parameter depends on method.
val sql = evaluateString(context,
call.valueArguments[if (call.isQueryMethod()) 0 else 1], true) ?: return

// Count only vararg arguments.
val argumentsCount = call.valueArgumentCount - if (call.isQueryMethod()) 1 else 2
val questionMarksCount = sql.count { it == '?' }
if (argumentsCount != questionMarksCount) {
val requiredArguments = "$questionMarksCount ${"argument".pluralize(questionMarksCount)}"
val actualArguments = "$argumentsCount ${"argument".pluralize(argumentsCount)}"
context.report(ISSUE, call, context.getLocation(call), "Wrong argument count, " +
"query $sql requires $requiredArguments, but was provided $actualArguments")
}
}
}

private fun UCallExpression.isQueryMethod() = methodName == QUERY_METHOD_NAME

private fun String.pluralize(count: Int) = if (count == 1) this else this + "s"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright (C) 2017 Square, Inc.
*
* 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
*
* http://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 com.squareup.sqlbrite3

import com.android.tools.lint.checks.infrastructure.TestFiles.java
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
import org.junit.Test

class SqlBriteArgCountDetectorTest {

companion object {
private val BRITE_DATABASE_STUB = java(
"""
package com.squareup.sqlbrite3;
public final class BriteDatabase {
public void query(String sql, Object... args) {
}
public void createQuery(String table, String sql, Object... args) {
}
// simulate createQuery with SupportSQLiteQuery query parameter
public void createQuery(String table, int something) {
}
}
""".trimIndent()
)
}

@Test
fun cleanCaseWithWithQueryAsLiteral() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
private static final String QUERY = "SELECT name FROM table WHERE id = ?";
public void test() {
BriteDatabase db = new BriteDatabase();
db.query(QUERY, "id");
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expectClean()
}

@Test
fun cleanCaseWithQueryAsBinaryExpression() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
private static final String QUERY = "SELECT name FROM table WHERE ";
public void test() {
BriteDatabase db = new BriteDatabase();
db.query(QUERY + "id = ?", "id");
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expectClean()
}

@Test
fun cleanCaseWithQueryThatCantBeEvaluated() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
private static final String QUERY = "SELECT name FROM table WHERE id = ?";
public void test() {
BriteDatabase db = new BriteDatabase();
db.query(query(), "id");
}
private String query() {
return QUERY + " age = ?";
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expectClean()
}

@Test
fun cleanCaseWithNonVarargMethodCall() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
public void test() {
BriteDatabase db = new BriteDatabase();
db.createQuery("table", 42);
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expectClean()
}

@Test
fun queryMethodWithWrongNumberOfArguments() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
private static final String QUERY = "SELECT name FROM table WHERE id = ?";
public void test() {
BriteDatabase db = new BriteDatabase();
db.query(QUERY);
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expect("src/test/pkg/Test.java:10: " +
"Error: Wrong argument count, query SELECT name FROM table WHERE id = ?" +
" requires 1 argument, but was provided 0 arguments [SqlBriteArgCount]\n" +
" db.query(QUERY);\n" +
" ~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
}

@Test
fun createQueryMethodWithWrongNumberOfArguments() {
lint().files(
BRITE_DATABASE_STUB,
java(
"""
package test.pkg;
import com.squareup.sqlbrite3.BriteDatabase;
public class Test {
private static final String QUERY = "SELECT name FROM table WHERE id = ?";
public void test() {
BriteDatabase db = new BriteDatabase();
db.createQuery("table", QUERY);
}
}
""".trimIndent()))
.issues(SqlBriteArgCountDetector.ISSUE)
.run()
.expect("src/test/pkg/Test.java:10: " +
"Error: Wrong argument count, query SELECT name FROM table WHERE id = ?" +
" requires 1 argument, but was provided 0 arguments [SqlBriteArgCount]\n" +
" db.createQuery(\"table\", QUERY);\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
}
}
2 changes: 2 additions & 0 deletions sqlbrite/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ dependencies {
androidTestImplementation rootProject.ext.supportTestRunner
androidTestImplementation rootProject.ext.truth
androidTestImplementation rootProject.ext.supportSqliteFramework

lintChecks project(':sqlbrite-lint')
}

android {
Expand Down

0 comments on commit 9ba06a9

Please sign in to comment.