├── docs └── pssqlClasses.md ├── examples ├── 1-tsql │ ├── .gitignore │ ├── src │ │ └── ExampleDatabase │ │ │ ├── ExampleDatabase │ │ │ ├── .gitignore │ │ │ ├── Billing.sql │ │ │ ├── Accelerator │ │ │ │ ├── Accelerator.sql │ │ │ │ ├── Particle.sql │ │ │ │ ├── SendHiggsBosonDiscoveryEmail.sql │ │ │ │ ├── IsExperimentReady.sql │ │ │ │ ├── AlertParticleDiscovered.sql │ │ │ │ └── GetStatusMessage.sql │ │ │ ├── BillingTests.sql │ │ │ ├── Billing │ │ │ │ ├── CustomerDiscounts.sql │ │ │ │ ├── Customers.sql │ │ │ │ ├── Orders.sql │ │ │ │ └── IsCustomerEligibleForDiscount.sql │ │ │ └── BillingTests │ │ │ │ ├── test that not existing customer can not get discount.sql │ │ │ │ └── test that customer with more than 3 orders and total over 2000 can discount.sql │ │ │ ├── ExampleDatabase.sln.DotSettings.user │ │ │ └── ExampleDatabase.sln │ ├── build │ │ └── sqlcoverresults │ │ │ └── Coverage.opencoverxml │ ├── sonar-project.properties │ ├── README.md │ ├── 01-executeDBChanges.ps1 │ ├── 00-installTools.ps1 │ └── 02-runSonar.ps1 ├── 5-tsql │ ├── .gitignore │ ├── executeAnalysis.ps1 │ ├── 01-git-clone.ps1 │ ├── README.md │ ├── sonar-project.properties │ ├── 00-installTools.ps1 │ └── 02-runSonar.ps1 ├── 4-vsql │ ├── src │ │ └── sample.sql │ └── sonar-project.properties ├── 7-snowflake │ ├── src │ │ └── sampleScript.sql │ └── sonar-project.properties ├── 6-pssql-with-custom-rules │ ├── src │ │ ├── testCommentViolation.sql │ │ └── testRandomOrderViolation.sql │ ├── sonar-project.properties │ └── rules │ │ └── codeCustomRules.customRules ├── 2-pssql │ ├── src │ │ └── sample.sql │ └── sonar-project.properties └── 3-mysql │ ├── src │ └── sample.sql │ └── sonar-project.properties ├── src ├── external │ ├── antlr4-grammar-sql-comments │ │ ├── .gitignore │ │ ├── examples │ │ │ ├── TestMultiline.sql │ │ │ └── TestSingleLine.sql │ │ └── src │ │ │ └── test │ │ │ └── java │ │ │ └── comments │ │ │ └── MainTest.java │ ├── target │ │ └── maven-status │ │ │ └── antlr4 │ │ │ └── .gitignore │ ├── Readme.md │ └── pom.xml ├── sonar-sql-plugin │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ ├── tsql │ │ │ │ │ ├── sample1.sql │ │ │ │ │ ├── sample2.sql │ │ │ │ │ └── rules1.xml │ │ │ │ ├── mysql │ │ │ │ │ └── sample1.sql │ │ │ │ ├── pssql │ │ │ │ │ └── sample1.sql │ │ │ │ └── external │ │ │ │ │ ├── rule1.customRules │ │ │ │ │ ├── rule3.customRules │ │ │ │ │ └── rule2.customRules │ │ │ └── java │ │ │ │ └── org │ │ │ │ ├── antlr │ │ │ │ └── sql │ │ │ │ │ ├── dialects │ │ │ │ │ └── SQLDialectRulesTest.java │ │ │ │ │ ├── tools │ │ │ │ │ ├── RulesToXmlPrinter.java │ │ │ │ │ ├── PluginRulesPrinter.java │ │ │ │ │ ├── PrettyPrinter.java │ │ │ │ │ ├── ClassesLister.java │ │ │ │ │ └── TestFilesGenerator.java │ │ │ │ │ └── sca │ │ │ │ │ ├── utils │ │ │ │ │ └── TestParsedNode.java │ │ │ │ │ ├── matchers │ │ │ │ │ └── IndexMatcherTest.java │ │ │ │ │ └── PSQLV2DialectTest.java │ │ │ │ └── sonar │ │ │ │ └── plugins │ │ │ │ └── sql │ │ │ │ ├── CGRulesDefinitionTest.java │ │ │ │ ├── MsRulesDefinitionTest.java │ │ │ │ ├── SQLRulesDefinitionTest.java │ │ │ │ ├── fillers │ │ │ │ └── CpdTokensFillerTest.java │ │ │ │ └── sensors │ │ │ │ ├── CGIssuesSensorTest.java │ │ │ │ ├── SQLCheckSensorTest.java │ │ │ │ └── MSIssuesSensorTest.java │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ ├── antlr │ │ │ └── sql │ │ │ │ ├── dialects │ │ │ │ ├── IDialect.java │ │ │ │ ├── comments │ │ │ │ │ ├── CommentsGrammar.tokens │ │ │ │ │ ├── CommentsGrammarLexer.tokens │ │ │ │ │ ├── CommentsGrammarVisitor.java │ │ │ │ │ ├── CommentsGrammar.interp │ │ │ │ │ ├── CommentsGrammarBaseVisitor.java │ │ │ │ │ └── CommentsGrammarListener.java │ │ │ │ ├── BaseDialect.java │ │ │ │ ├── DialectLanguageTypesMap.java │ │ │ │ ├── MySqlDialect.java │ │ │ │ ├── VSQLDialect.java │ │ │ │ ├── TSQLDialect.java │ │ │ │ ├── psqlv2 │ │ │ │ │ ├── PostgreSQLLexerBase.java │ │ │ │ │ ├── ParserDispatchingErrorListener.java │ │ │ │ │ └── LexerDispatchingErrorListener.java │ │ │ │ ├── SnowflakeDialect.java │ │ │ │ ├── PsSqlV2Dialect.java │ │ │ │ ├── PsSqlDialect.java │ │ │ │ ├── Dialects.java │ │ │ │ └── rules │ │ │ │ │ └── CommonRules.java │ │ │ │ ├── sca │ │ │ │ ├── matchers │ │ │ │ │ ├── IMatcher.java │ │ │ │ │ ├── INodesMatcher.java │ │ │ │ │ ├── ClassNameMatcher.java │ │ │ │ │ ├── DistanceMatcher.java │ │ │ │ │ ├── TextMatcher.java │ │ │ │ │ ├── DefaultNodesMatcher.java │ │ │ │ │ └── IndexMatcher.java │ │ │ │ └── nodes │ │ │ │ │ └── IParsedNode.java │ │ │ │ ├── visitors │ │ │ │ ├── ClassTypesCountingVisitor.java │ │ │ │ └── RulesMatchingVisitor2.java │ │ │ │ └── models │ │ │ │ └── AntlrContext.java │ │ │ └── sonar │ │ │ └── plugins │ │ │ └── sql │ │ │ ├── fillers │ │ │ ├── Filler.java │ │ │ ├── IssuesFiller.java │ │ │ ├── CyclomaticComplexityFiller.java │ │ │ ├── CognitiveComplexityFiller.java │ │ │ ├── CpdTokensFiller.java │ │ │ ├── LineMetricsFiller.java │ │ │ ├── CommentIssuesFiller.java │ │ │ └── HighlighterFiller.java │ │ │ ├── SQLLanguage.java │ │ │ ├── SQLQualityProfile.java │ │ │ ├── issues │ │ │ └── SqlIssuesList.java │ │ │ ├── CaseChangingCharStream.java │ │ │ ├── models │ │ │ └── rules │ │ │ │ ├── RuleMode.java │ │ │ │ ├── TextCheckType.java │ │ │ │ ├── Names.java │ │ │ │ ├── TextToFind.java │ │ │ │ ├── RuleDistanceIndexMatchType.java │ │ │ │ ├── RuleMatchType.java │ │ │ │ ├── CompliantRulesCodeExamples.java │ │ │ │ ├── ViolatingRulesCodeExamples.java │ │ │ │ ├── UsesRules.java │ │ │ │ ├── ParentRules.java │ │ │ │ ├── ChildrenRules.java │ │ │ │ ├── SiblingsRules.java │ │ │ │ └── RuleResultType.java │ │ │ ├── Constants.java │ │ │ ├── SQLRulesDefinition.java │ │ │ ├── sensors │ │ │ └── XmlHelper.java │ │ │ └── MsRulesDefinition.java │ ├── .gitignore │ └── examples │ │ ├── mysql │ │ └── src │ │ │ ├── c001.sql │ │ │ ├── c004.sql │ │ │ ├── c003.sql │ │ │ ├── c017.sql │ │ │ ├── c002.sql │ │ │ ├── c014.sql │ │ │ ├── c015.sql │ │ │ ├── c016.sql │ │ │ ├── c012.sql │ │ │ └── c009.sql │ │ ├── pssql │ │ └── src │ │ │ ├── c001.sql │ │ │ ├── c004.sql │ │ │ ├── c017.sql │ │ │ ├── c014.sql │ │ │ ├── c015.sql │ │ │ ├── c016.sql │ │ │ ├── c012.sql │ │ │ └── c009.sql │ │ ├── tsql │ │ └── src │ │ │ ├── c001.sql │ │ │ ├── c004.sql │ │ │ ├── c007.sql │ │ │ ├── c013.sql │ │ │ ├── c003.sql │ │ │ ├── c005.sql │ │ │ ├── c017.sql │ │ │ ├── c002.sql │ │ │ ├── c014.sql │ │ │ ├── c015.sql │ │ │ ├── c011.sql │ │ │ ├── c016.sql │ │ │ ├── c010.sql │ │ │ ├── c012.sql │ │ │ └── c009.sql │ │ ├── other │ │ └── src │ │ │ └── c030.sql │ │ └── vsql │ │ └── src │ │ ├── c021.sql │ │ └── c020.sql └── .gitmodules ├── .gitignore ├── azure-pipelines.yml └── .devcontainer ├── devcontainer.json └── dockerfile /docs/pssqlClasses.md: -------------------------------------------------------------------------------- 1 | # PSSQL 2 | Supported classes: 3 | -------------------------------------------------------------------------------- /examples/1-tsql/.gitignore: -------------------------------------------------------------------------------- 1 | /tools 2 | /src 3 | /sql-server-samples -------------------------------------------------------------------------------- /examples/5-tsql/.gitignore: -------------------------------------------------------------------------------- 1 | /tools 2 | /src 3 | /sql-server-samples -------------------------------------------------------------------------------- /src/external/antlr4-grammar-sql-comments/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /examples/4-vsql/src/sample.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM temp1; 2 | 3 | SELECT 1; 4 | -------------------------------------------------------------------------------- /src/external/target/maven-status/antlr4/.gitignore: -------------------------------------------------------------------------------- 1 | /dependencies.ser 2 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/tsql/sample1.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM 2 | dbo.test; -------------------------------------------------------------------------------- /examples/7-snowflake/src/sampleScript.sql: -------------------------------------------------------------------------------- 1 | SELECT name, * FROM test_table order by name desc; 2 | -------------------------------------------------------------------------------- /examples/5-tsql/executeAnalysis.ps1: -------------------------------------------------------------------------------- 1 | ./00-installTools.ps1 2 | ./01-git-clone.ps1 3 | ./02-runSonar.ps1 -------------------------------------------------------------------------------- /src/sonar-sql-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.classpath 4 | /.project 5 | /out.xml 6 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/mysql/sample1.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM sample.test order by 1; 2 | -------------------------------------------------------------------------------- /examples/6-pssql-with-custom-rules/src/testCommentViolation.sql: -------------------------------------------------------------------------------- 1 | SELECT name FROM sample.test order by name desc; 2 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/.gitignore: -------------------------------------------------------------------------------- 1 | /ExampleDatabase.sqlproj.user 2 | **/bin/**/* 3 | **/obj/**/* 4 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Billing.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA [Billing] 2 | AUTHORIZATION [dbo]; 3 | 4 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c001.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | /*violating code*/ 3 | SELECT SLEEP(5); 4 | SELECT SLEEP(5); 5 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c001.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | /*violating code*/ 3 | SELECT PG_SLEEP(5); 4 | SELECT PG_SLEEP(5); 5 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c001.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | /*violating code*/ 3 | WAITFOR '10:00:00'; 4 | WAITFOR '10:00:00'; 5 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/Accelerator.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA [Accelerator] 2 | AUTHORIZATION [dbo]; 3 | 4 | -------------------------------------------------------------------------------- /examples/6-pssql-with-custom-rules/src/testRandomOrderViolation.sql: -------------------------------------------------------------------------------- 1 | /** 2 | Example 3 | */ 4 | SELECT name FROM sample.test order by random() DESC; 5 | -------------------------------------------------------------------------------- /src/external/antlr4-grammar-sql-comments/examples/TestMultiline.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT * FROM Products; -- my test 3 | select /* sample */ 1; 4 | // my line test 5 | -------------------------------------------------------------------------------- /examples/2-pssql/src/sample.sql: -------------------------------------------------------------------------------- 1 | -- sample 2 | SELECT * FROM sample.test order by 1; 3 | 4 | 5 | SELECT name,name FROM test; 6 | /* 7 | end comment 8 | */ 9 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/Particle.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [Accelerator].[Particle] 2 | ( 3 | [Id] INT NOT NULL PRIMARY KEY 4 | ) 5 | -------------------------------------------------------------------------------- /src/external/antlr4-grammar-sql-comments/examples/TestSingleLine.sql: -------------------------------------------------------------------------------- 1 | /* AUTHOR: test 2 | DATE: 2000-10-10 3 | DESCRIPTION: MyTest 4 | */ 5 | SELECT * FROM Products; -------------------------------------------------------------------------------- /examples/1-tsql/build/sqlcoverresults/Coverage.opencoverxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gretard/sonar-sql-plugin/HEAD/examples/1-tsql/build/sqlcoverresults/Coverage.opencoverxml -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/tsql/sample2.sql: -------------------------------------------------------------------------------- 1 | SELECT 1; 2 | -- sample comment 3 | /* 4 | * Comment 2 5 | */ 6 | 7 | 8 | SELECT 2; -- inline comment 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/3-mysql/src/sample.sql: -------------------------------------------------------------------------------- 1 | -- sample 2 | SELECT * FROM sample.test order by 1; 3 | SELECT name,name FROM test; 4 | /* 5 | end comment 6 | */ 7 | # more comments 8 | 9 | /*! MySQL-specific comment */ -------------------------------------------------------------------------------- /examples/5-tsql/01-git-clone.ps1: -------------------------------------------------------------------------------- 1 | git clone https://github.com/microsoft/sql-server-samples.git 2 | $baseDir = "$PSScriptRoot"; 3 | Copy-Item -Path "$baseDir\sql-server-samples" -Filter "*.sql" -Destination "$baseDir\src" -Recurse -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/IDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.models.AntlrContext; 4 | 5 | public interface IDialect { 6 | public AntlrContext parse(String text); 7 | } 8 | -------------------------------------------------------------------------------- /examples/3-mysql/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.mysql.project 2 | sonar.projectName=examples.sql.mysql.project 3 | sonar.projectVersion=1.1 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | sonar.sql.dialect=mysql -------------------------------------------------------------------------------- /examples/4-vsql/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.vsql.project 2 | sonar.projectName=examples.sql.vsql.project 3 | sonar.projectVersion=1.1 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | sonar.sql.dialect=vsql 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c004.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test order by name; 3 | SELECT * from dbo.test order by name; 4 | /*violating code*/ 5 | SELECT * from dbo.test order by 1, 2; 6 | SELECT * from dbo.test order by 1, 2; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c004.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test order by name; 3 | SELECT * from dbo.test order by name; 4 | /*violating code*/ 5 | SELECT * from dbo.test order by 1, 2; 6 | SELECT * from dbo.test order by 1, 2; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c004.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test order by name; 3 | SELECT * from dbo.test order by name; 4 | /*violating code*/ 5 | SELECT * from dbo.test order by 1, 2; 6 | SELECT * from dbo.test order by 1, 2; 7 | -------------------------------------------------------------------------------- /src/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/grammars-v4"] 2 | path = external/grammars-v4 3 | url = https://github.com/antlr/grammars-v4.git 4 | [submodule "external/antlr_psql"] 5 | path = external/antlr_psql 6 | url = https://github.com/tshprecher/antlr_psql.git 7 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/BillingTests.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA [BillingTests] 2 | AUTHORIZATION [dbo]; 3 | 4 | 5 | GO 6 | EXECUTE sp_addextendedproperty @name = N'tSQLt.TestClass', @value = 1, @level0type = N'SCHEMA', @level0name = N'BillingTests'; 7 | 8 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c007.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test; 3 | SELECT name, surname from dbo.test; 4 | /*violating code*/ 5 | SELECT name, surname from dbo.test WITH (NOLOCK); 6 | SELECT name, surname from dbo.test WITH (NOLOCK); 7 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/SendHiggsBosonDiscoveryEmail.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE PROCEDURE Accelerator.SendHiggsBosonDiscoveryEmail 3 | @EmailAddress NVARCHAR(MAX) 4 | AS 5 | BEGIN 6 | RAISERROR('Not Implemented - yet',16,10); 7 | END; 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Billing/CustomerDiscounts.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW [Billing].[CustomerDiscounts] 2 | AS 3 | SELECT *, CASE WHEN [Billing].IsCustomerEligibleForDiscount(id) = 1 THEN 'discount available' ELSE 'no discount' END AS 'discount options' 4 | FROM billing.Customers -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c013.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | CREATE UNIQUE INDEX IX_Test_Name on dbo.test (Name); 3 | CREATE UNIQUE INDEX IX_Test_Name on dbo.test (Name); 4 | /*violating code*/ 5 | CREATE UNIQUE INDEX Test_Name on dbo.test (Name); 6 | CREATE UNIQUE INDEX Test_Name on dbo.test (Name); 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/other/src/c030.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | /* AUTHOR: test 3 | Date: 2020-01-01 4 | */ 5 | SELECT * FROM test_table1; 6 | /* AUTHOR: test 7 | Date: 2020-01-01 8 | */ 9 | SELECT * FROM test_table1; 10 | /*violating code*/ 11 | SELECT * FROM test_table1; 12 | SELECT * FROM test_table1; 13 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c003.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | INSERT INTO dbo.test (a,b) VALUES (1,2); 3 | INSERT INTO dbo.test (a,b) VALUES (1,2); 4 | /*violating code*/ 5 | INSERT INTO dbo.test VALUES (1,2); 6 | INSERT INTO dbo.test2 VALUES (1,2); 7 | INSERT INTO dbo.test VALUES (1,2); 8 | INSERT INTO dbo.test2 VALUES (1,2); 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c003.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | INSERT INTO dbo.test (a,b) VALUES (1,2); 3 | INSERT INTO dbo.test (a,b) VALUES (1,2); 4 | /*violating code*/ 5 | INSERT INTO dbo.test VALUES (1,2); 6 | INSERT INTO dbo.test2 VALUES (1,2); 7 | INSERT INTO dbo.test VALUES (1,2); 8 | INSERT INTO dbo.test2 VALUES (1,2); 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/vsql/src/c021.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT 1; DELETE FROM temp1; COMMIT; SELECT 2; 3 | SELECT 1; DELETE FROM temp1; COMMIT; SELECT 2; 4 | /*violating code*/ 5 | SELECT 1; DELETE FROM temp1; SELECT 2; 6 | SELECT 1; DELETE FROM temp1; SELECT 2; 7 | COMMIT; DELETE FROM temp1; 8 | COMMIT; DELETE FROM temp1; 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/IMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 5 | 6 | public interface IMatcher { 7 | boolean match(IParsedNode item, RuleImplementation ruleImplementation); 8 | } 9 | -------------------------------------------------------------------------------- /examples/2-pssql/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.pssql.project 2 | sonar.projectName=examples.sql.pssql.project 3 | sonar.projectVersion=1.1 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | sonar.sql.dialect=pssqlv2 9 | # uncomment this line to use old version of PSQL dialect 10 | #sonar.sql.dialect=pssql 11 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c005.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | EXECUTE sp_executesql N'select 1'; 3 | EXECUTE sp_executesql N'select 1'; 4 | exec sys.sp_test @test = 'Publisher'; 5 | exec sys.sp_test @test = 'Publisher'; 6 | /*violating code*/ 7 | EXEC ('SELECT 1'); 8 | EXEC ('SELECT 1'); 9 | EXEC (@sQueryText); 10 | EXEC (@sQueryText); 11 | -------------------------------------------------------------------------------- /examples/5-tsql/README.md: -------------------------------------------------------------------------------- 1 | # TSQL Sample project 2 | Analyses project from https://github.com/microsoft/sql-server-samples 3 | 4 | ## Execute analysis 5 | 1. Update **sonar-project.properties** with correct credentials 6 | 2. Run: ```powershell -File executeAnalysis.ps1``` 7 | 8 | ## Requirements 9 | Required tools: 10 | 11 | - git 12 | - powershell 13 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c017.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test order by name desc, surname asc; 3 | SELECT name, surname from dbo.test order by name desc, surname asc; 4 | /*violating code*/ 5 | SELECT name, surname from dbo.test order by name, surname asc; 6 | SELECT name, surname from dbo.test order by name, surname asc; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c017.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test order by name desc, surname asc; 3 | SELECT name, surname from dbo.test order by name desc, surname asc; 4 | /*violating code*/ 5 | SELECT name, surname from dbo.test order by name, surname asc; 6 | SELECT name, surname from dbo.test order by name, surname asc; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c017.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test order by name desc, surname asc; 3 | SELECT name, surname from dbo.test order by name desc, surname asc; 4 | /*violating code*/ 5 | SELECT name, surname from dbo.test order by name, surname asc; 6 | SELECT name, surname from dbo.test order by name, surname asc; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/INodesMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 5 | 6 | public interface INodesMatcher { 7 | boolean matches(IParsedNode item, IParsedNode parent, RuleImplementation rule); 8 | } 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/vsql/src/c020.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * FROM test_table1; 3 | SELECT * FROM test_table1; 4 | /*violating code*/ 5 | SELECT /*+DIRECT*/ * FROM test_table1; 6 | SELECT /*+DIRECT*/ * FROM test_table1; 7 | CREATE TABLE test_table2 AS SELECT /*+DIRECT*/ * FROM test_table1; 8 | CREATE TABLE test_table2 AS SELECT /*+DIRECT*/ * FROM test_table1; 9 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/IsExperimentReady.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE FUNCTION Accelerator.IsExperimentReady() 3 | RETURNS BIT 4 | AS 5 | BEGIN 6 | DECLARE @NumParticles INT; 7 | 8 | SELECT @NumParticles = COUNT(1) FROM Accelerator.Particle; 9 | 10 | IF @NumParticles > 2 11 | RETURN 1; 12 | 13 | RETURN 0; 14 | END; 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/Filler.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import org.antlr.sql.models.AntlrContext; 4 | import org.sonar.api.batch.fs.InputFile; 5 | import org.sonar.api.batch.sensor.SensorContext; 6 | 7 | public interface Filler { 8 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext); 9 | } 10 | -------------------------------------------------------------------------------- /examples/1-tsql/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.tsql.project 2 | sonar.projectName=examples.sql.tsql.project 3 | sonar.projectVersion=1.0 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | # optional as plugin defaults to tsql 9 | sonar.sql.dialect=tsql 10 | 11 | # change these 12 | sonar.login=admin 13 | sonar.password=admin 14 | 15 | sonar.scm.disabled=true 16 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/AlertParticleDiscovered.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE PROCEDURE Accelerator.AlertParticleDiscovered 3 | @ParticleDiscovered NVARCHAR(MAX) 4 | AS 5 | BEGIN 6 | IF @ParticleDiscovered = 'Higgs Boson' 7 | BEGIN 8 | EXEC Accelerator.SendHiggsBosonDiscoveryEmail 'particle-discovery@new-era-particles.tsqlt.org'; 9 | END; 10 | END; 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Accelerator/GetStatusMessage.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE FUNCTION Accelerator.GetStatusMessage() 3 | RETURNS NVARCHAR(MAX) 4 | AS 5 | BEGIN 6 | DECLARE @NumParticles INT; 7 | SELECT @NumParticles = COUNT(1) FROM Accelerator.Particle; 8 | RETURN 'The Accelerator is prepared with ' + CAST(@NumParticles AS NVARCHAR(MAX)) + ' particles.'; 9 | END; 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/5-tsql/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.tsql.sql-server-samples 2 | sonar.projectName=examples.sql.tsql.sql-server-samples 3 | sonar.projectVersion=1.0 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | # optional as plugin defaults to tsql 9 | sonar.sql.dialect=tsql 10 | 11 | # change these 12 | sonar.login=admin 13 | sonar.password=admin 14 | 15 | sonar.scm.disabled=true -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c002.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test; 3 | SELECT name, surname from dbo.test; 4 | SELECT name, surname, 1 * 3 from dbo.test; 5 | SELECT name, surname, 1 * 3 from dbo.test; 6 | /*violating code*/ 7 | SELECT t1.*, t2.* from dbo.test as t1 inner join dbo.test2 as t2 on t1.id=t2.id; 8 | SELECT t1.*, t2.* from dbo.test as t1 inner join dbo.test2 as t2 on t1.id=t2.id; 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c002.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname from dbo.test; 3 | SELECT name, surname from dbo.test; 4 | SELECT name, surname, 1 * 3 from dbo.test; 5 | SELECT name, surname, 1 * 3 from dbo.test; 6 | /*violating code*/ 7 | SELECT t1.*, t2.* from dbo.test as t1 inner join dbo.test2 as t2 on t1.id=t2.id; 8 | SELECT t1.*, t2.* from dbo.test as t1 inner join dbo.test2 as t2 on t1.id=t2.id; 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c014.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 3 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 6 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c014.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 3 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 6 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c014.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 3 | SELECT name, surname, count from dbo.test where name = 'or' and surname = 'TestOR'; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 6 | SELECT name, surname, count from dbo.test where name = 'Test' OR surname = 'Testor'; 7 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Billing/Customers.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [Billing].[Customers] ( 2 | [id] INT NULL, 3 | [name] NCHAR (255) NULL 4 | ); 5 | 6 | 7 | GO 8 | EXECUTE sp_addextendedproperty @name = N'tSQLt.FakeTable_OrgTableName', @value = N'tSQLt_tempobject_9ebb1c5d4b6944daace9941ebca2c6bc', @level0type = N'SCHEMA', @level0name = N'Billing', @level1type = N'TABLE', @level1name = N'Customers'; 9 | 10 | -------------------------------------------------------------------------------- /examples/7-snowflake/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=examples.sql.snowflake.project 2 | sonar.projectName=examples.sql.snowflake.project 3 | sonar.projectVersion=1.1 4 | sonar.sources=src 5 | # optional 6 | sonar.language=sql 7 | sonar.sql.dialect=snowflake 8 | 9 | # change these 10 | sonar.login=admin 11 | sonar.password=admin2 12 | 13 | # comment this if not using https://github.com/felipebz/zpa plugin 14 | sonar.lang.patterns.plsqlopen=na -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c015.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 3 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 6 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c015.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 3 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 6 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c015.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 3 | SELECT name, surname, count from dbo.test union all SELECT name, surname, count from dbo.test2; 4 | /*violating code*/ 5 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 6 | SELECT name, surname, count from dbo.test union SELECT name, surname, count from dbo.test2; 7 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c011.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | ALTER TABLE dbo.Orders ADD CONSTRAINT FK_ClientId FOREIGN KEY (ClientId) REFERENCES dbo.Clients(Id); 3 | ALTER TABLE dbo.Orders ADD CONSTRAINT FK_ClientId FOREIGN KEY (ClientId) REFERENCES dbo.Clients(Id); 4 | /*violating code*/ 5 | ALTER TABLE dbo.Orders ADD CONSTRAINT ClientId FOREIGN KEY (ClientId) REFERENCES dbo.Clients(Id); 6 | ALTER TABLE dbo.Orders ADD CONSTRAINT ClientId FOREIGN KEY (ClientId) REFERENCES dbo.Clients(Id); 7 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/BillingTests/test that not existing customer can not get discount.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE PROCEDURE [BillingTests].[test that not existing customer can not get discount] 3 | AS 4 | BEGIN 5 | EXEC tSQLt.FakeTable 'Billing.Customers'; 6 | EXEC tSQLt.FakeTable 'Billing.Orders'; 7 | DECLARE @actualComputedResult BIT; 8 | SET @actualComputedResult = Billing.IsCustomerEligibleForDiscount(1); 9 | EXEC tSQLt.AssertEquals 0, @actualComputedResult; 10 | END; -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Billing/Orders.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [Billing].[Orders] ( 2 | [ordername] NCHAR (255) NULL, 3 | [customerId] INT NULL, 4 | [sum] FLOAT (53) NULL, 5 | [date] DATE NULL 6 | ); 7 | 8 | 9 | GO 10 | EXECUTE sp_addextendedproperty @name = N'tSQLt.FakeTable_OrgTableName', @value = N'tSQLt_tempobject_f0c30fdb1c4a4fa697445d265ee80788', @level0type = N'SCHEMA', @level0name = N'Billing', @level1type = N'TABLE', @level1name = N'Orders'; 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .scannerwork 26 | examples/1-tsql/tools 27 | *.jfm 28 | *.dbmdl 29 | *.suo 30 | .settings 31 | .project 32 | .classpath 33 | 34 | **/tools/** 35 | 36 | src/sonar-sql-plugin/examples/** 37 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/Billing/IsCustomerEligibleForDiscount.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION Billing.IsCustomerEligibleForDiscount 2 | ( 3 | @customerId int 4 | ) 5 | RETURNS bit 6 | AS 7 | BEGIN 8 | DECLARE @result bit = 0 9 | DECLARE @date DATETIME = GETDATE(); 10 | DECLARE @prevMonth DATETIME = EOMONTH ( @date, -1 ); 11 | 12 | SELECT @result = case when sum([sum]) > 2000 and count(*) > 3 then 1 else 0 end 13 | FROM Billing.Orders WHERE customerId = @customerId and [date] between @prevMonth and @date 14 | 15 | RETURN @result 16 | 17 | END -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/dialects/SQLDialectRulesTest.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class SQLDialectRulesTest { 7 | 8 | @Test 9 | public void testGetRules() { 10 | long s = SQLDialectRules.INSTANCE.getGroupedRules().get(0).getRule().size(); 11 | Assert.assertEquals(20, s); 12 | } 13 | 14 | @Test 15 | public void testGetRules2() { 16 | long s = SQLDialectRules.INSTANCE.getRules().get(0).getRule().size(); 17 | Assert.assertEquals(16, s); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c016.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 3 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 4 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 5 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 6 | /*violating code*/ 7 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 8 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c016.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 3 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 4 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 5 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 6 | /*violating code*/ 7 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 8 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c016.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 3 | SELECT name, surname, count from dbo.test where locationID in (1,2,3); 4 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 5 | SELECT name, surname, count from dbo.test where exists (select 1 from dbo.locations where id = locationID); 6 | /*violating code*/ 7 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 8 | SELECT name, surname, count from dbo.test where locationID in (select id from dbo.locations); 9 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c010.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | ALTER TABLE dbo.Orders ADD CONSTRAINT PK_OrderId PRIMARY KEY CLUSTERED (Id); 3 | ALTER TABLE dbo.Orders ADD CONSTRAINT PK_OrderId PRIMARY KEY CLUSTERED (Id); 4 | /*violating code*/ 5 | CREATE TABLE dbo.Orders 6 | ( 7 | Id int NOT NULL, 8 | CONSTRAINT OrderID PRIMARY KEY CLUSTERED (Id) 9 | ); 10 | CREATE TABLE dbo.Orders 11 | ( 12 | Id int NOT NULL, 13 | CONSTRAINT OrderID PRIMARY KEY CLUSTERED (Id) 14 | ); 15 | CREATE TABLE dbo.Orders 16 | ( 17 | Id int NOT NULL, 18 | PRIMARY KEY (Id) 19 | ); 20 | CREATE TABLE dbo.Orders 21 | ( 22 | Id int NOT NULL, 23 | PRIMARY KEY (Id) 24 | ); 25 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/pssql/sample1.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | -- Dumped from database version 9.6.3 6 | -- Dumped by pg_dump version 10beta1 7 | 8 | SET statement_timeout = 0; 9 | SET lock_timeout = 0; 10 | SET idle_in_transaction_session_timeout = 0; 11 | SET client_encoding = 'UTF8'; 12 | SET standard_conforming_strings = on; 13 | SET check_function_bodies = false; 14 | SET client_min_messages = warning; 15 | SET row_security = off; 16 | 17 | -- 18 | -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: 19 | -- 20 | 21 | CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; 22 | 23 | 24 | SELECT * FROM sample.test order by 1; 25 | -------------------------------------------------------------------------------- /examples/6-pssql-with-custom-rules/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=pssql.custom.rules 2 | sonar.projectName=Test PSSQL custom rules project 3 | sonar.projectVersion=1.0 4 | sonar.sources=src 5 | 6 | # optional 7 | sonar.language=sql 8 | sonar.sql.dialect=pssqlv2 9 | # uncomment this line to use old version of PSQL dialect 10 | #sonar.sql.dialect=pssql 11 | 12 | 13 | # change these 14 | sonar.login=admin 15 | sonar.password=admin 16 | 17 | # by default whole directory will be searched (.), you can override this by specifying search path 18 | #sonar.sql.rules.path=./rules 19 | 20 | # comment this if not using https://github.com/felipebz/zpa plugin 21 | sonar.lang.patterns.plsqlopen=na 22 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c012.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test where name IS NULL; 3 | SELECT * from dbo.test where name IS NULL; 4 | SELECT * from dbo.test where name IS NOT NULL; 5 | SELECT * from dbo.test where name IS NOT NULL; 6 | SELECT * from dbo.test where name = 'test'; 7 | SELECT * from dbo.test where name = 'test'; 8 | /*violating code*/ 9 | SELECT * from dbo.test where name = null and surname = 'Test' ; 10 | SELECT * from dbo.test where name = null and surname = 'Test' ; 11 | SELECT * from dbo.test where name != null; 12 | SELECT * from dbo.test where name != null; 13 | SELECT * from dbo.test where name <> null; 14 | SELECT * from dbo.test where name <> null; 15 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c012.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test where name IS NULL; 3 | SELECT * from dbo.test where name IS NULL; 4 | SELECT * from dbo.test where name IS NOT NULL; 5 | SELECT * from dbo.test where name IS NOT NULL; 6 | SELECT * from dbo.test where name = 'test'; 7 | SELECT * from dbo.test where name = 'test'; 8 | /*violating code*/ 9 | SELECT * from dbo.test where name = null and surname = 'Test' ; 10 | SELECT * from dbo.test where name = null and surname = 'Test' ; 11 | SELECT * from dbo.test where name != null; 12 | SELECT * from dbo.test where name != null; 13 | SELECT * from dbo.test where name <> null; 14 | SELECT * from dbo.test where name <> null; 15 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c012.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT * from dbo.test where name IS NULL; 3 | SELECT * from dbo.test where name IS NULL; 4 | SELECT * from dbo.test where name IS NOT NULL; 5 | SELECT * from dbo.test where name IS NOT NULL; 6 | SELECT * from dbo.test where name = 'test'; 7 | SELECT * from dbo.test where name = 'test'; 8 | /*violating code*/ 9 | SELECT * from dbo.test where name = null and surname = 'Test' ; 10 | SELECT * from dbo.test where name = null and surname = 'Test' ; 11 | SELECT * from dbo.test where name != null; 12 | SELECT * from dbo.test where name != null; 13 | SELECT * from dbo.test where name <> null; 14 | SELECT * from dbo.test where name <> null; 15 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/CGRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | import org.sonar.api.server.rule.RulesDefinition; 7 | import org.sonar.api.server.rule.RulesDefinition.Context; 8 | 9 | public class CGRulesDefinitionTest { 10 | 11 | @Test 12 | @Ignore 13 | public void testDefine() { 14 | Context context = new RulesDefinition.Context(); 15 | CGRulesDefinition sut = new CGRulesDefinition(); 16 | sut.define(context); 17 | 18 | Assert.assertEquals(1, context.repositories().size()); 19 | Assert.assertEquals(14, context.repositories().get(0).rules().size()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/mysql/src/c009.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 3 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 4 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 5 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 6 | SELECT max(price) from dbo.items; 7 | SELECT max(price) from dbo.items; 8 | /*violating code*/ 9 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 10 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 11 | SELECT name, surname from dbo.test where name like '%red' 12 | SELECT name, surname from dbo.test where name like '%red' 13 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/pssql/src/c009.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 3 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 4 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 5 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 6 | SELECT max(price) from dbo.items; 7 | SELECT max(price) from dbo.items; 8 | /*violating code*/ 9 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 10 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 11 | SELECT name, surname from dbo.test where name like '%red' 12 | SELECT name, surname from dbo.test where name like '%red' 13 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/examples/tsql/src/c009.sql: -------------------------------------------------------------------------------- 1 | /*compliant code*/ 2 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 3 | SELECT MAX(RateChangeDate) FROM HumanResources.EmployeePayHistory WHERE BusinessEntityID = 1 4 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 5 | SELECT name, surname from dbo.test where date between 2008-10-10 and 2010-10-10; 6 | SELECT max(price) from dbo.items; 7 | SELECT max(price) from dbo.items; 8 | /*violating code*/ 9 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 10 | SELECT name, surname from dbo.test where year(date) > 2008 and month = 12; 11 | SELECT name, surname from dbo.test where name like '%red' 12 | SELECT name, surname from dbo.test where name like '%red' 13 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/ClassNameMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.sca.nodes.IParsedNode; 5 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 6 | import org.sonar.plugins.sql.models.rules.RuleMatchType; 7 | 8 | public class ClassNameMatcher implements IMatcher { 9 | 10 | @Override 11 | public boolean match(IParsedNode item, RuleImplementation ruleImplementation) { 12 | if (ruleImplementation.getRuleMatchType() == RuleMatchType.TEXT_ONLY) { 13 | return true; 14 | } 15 | final List names = ruleImplementation.getNames().getTextItem(); 16 | boolean result = names.contains(item.getClassName()); 17 | return result; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/tools/RulesToXmlPrinter.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.tools; 2 | 3 | import java.io.File; 4 | import javax.xml.bind.JAXBContext; 5 | import javax.xml.bind.JAXBException; 6 | import javax.xml.bind.Marshaller; 7 | import org.antlr.sql.dialects.rules.CommonRules; 8 | import org.sonar.plugins.sql.models.rules.SqlRules; 9 | 10 | public class RulesToXmlPrinter { 11 | 12 | public static void main(String[] args) throws JAXBException { 13 | SqlRules rule = CommonRules.INSTANCE.getRules().get(0); 14 | 15 | writeRules(rule); 16 | } 17 | 18 | public static void writeRules(SqlRules rule) throws JAXBException { 19 | JAXBContext context = JAXBContext.newInstance(SqlRules.class); 20 | Marshaller marshaller = context.createMarshaller(); 21 | marshaller.marshal(rule, new File("out.xml")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/SQLLanguage.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import org.sonar.api.config.Configuration; 4 | import org.sonar.api.resources.AbstractLanguage; 5 | 6 | public final class SQLLanguage extends AbstractLanguage { 7 | 8 | public static final String NAME = "SQL"; 9 | 10 | public static final String[] DEFAULT_FILE_SUFFIXES = new String[] {".sql"}; 11 | 12 | private final Configuration config; 13 | 14 | public SQLLanguage(final Configuration config) { 15 | super(Constants.languageKey, NAME); 16 | this.config = config; 17 | } 18 | 19 | public String[] getFileSuffixes() { 20 | 21 | final String[] suffixes = config.getStringArray(Constants.PLUGIN_SUFFIXES); 22 | if (suffixes == null || suffixes.length == 0) { 23 | return DEFAULT_FILE_SUFFIXES; 24 | } 25 | return suffixes; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/MsRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.sonar.api.rules.RuleType; 6 | import org.sonar.api.server.rule.RulesDefinition; 7 | import org.sonar.api.server.rule.RulesDefinition.Context; 8 | 9 | public class MsRulesDefinitionTest { 10 | 11 | @Test 12 | public void test() { 13 | Context context = new RulesDefinition.Context(); 14 | MsRulesDefinition sut = new MsRulesDefinition(); 15 | sut.define(context); 16 | 17 | Assert.assertEquals(1, context.repositories().size()); 18 | Assert.assertEquals(14, context.repositories().get(0).rules().size()); 19 | 20 | Assert.assertTrue( 21 | context.repositories().get(0).rules().stream() 22 | .allMatch(x -> x.type() == RuleType.CODE_SMELL)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/SQLRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.sonar.api.rules.RuleType; 6 | import org.sonar.api.server.rule.RulesDefinition; 7 | import org.sonar.api.server.rule.RulesDefinition.Context; 8 | 9 | public class SQLRulesDefinitionTest { 10 | 11 | @Test 12 | public void test() { 13 | Context context = new RulesDefinition.Context(); 14 | SQLRulesDefinition sut = new SQLRulesDefinition(); 15 | sut.define(context); 16 | 17 | Assert.assertEquals(1, context.repositories().size()); 18 | Assert.assertEquals(20, context.repositories().get(0).rules().size()); 19 | Assert.assertTrue( 20 | context.repositories().get(0).rules().stream() 21 | .allMatch(x -> x.type() == RuleType.CODE_SMELL)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | strategy: 2 | matrix: 3 | linux: 4 | imageName: 'ubuntu-latest' 5 | mac: 6 | imageName: 'macos-latest' 7 | windows: 8 | imageName: 'windows-latest' 9 | 10 | pool: 11 | vmImage: $(imageName) 12 | demands: maven 13 | 14 | steps: 15 | - task: JavaToolInstaller@0 16 | inputs: 17 | versionSpec: '11' 18 | jdkArchitectureOption: 'x64' 19 | jdkSourceOption: 'PreInstalled' 20 | 21 | - task: Maven@3 22 | displayName: 'Maven sonar-sql-plugin install' 23 | inputs: 24 | mavenPomFile: 'src/sonar-sql-plugin/pom.xml' 25 | 26 | - task: CopyFiles@2 27 | displayName: 'Copy Files to: $(build.artifactstagingdirectory)' 28 | inputs: 29 | SourceFolder: '$(system.defaultworkingdirectory)' 30 | Contents: '**/sonar-sql-plugin*.jar' 31 | TargetFolder: '$(build.artifactstagingdirectory)' 32 | 33 | - task: PublishBuildArtifacts@1 34 | inputs: 35 | pathtoPublish: '$(Build.artifactstagingdirectory)' 36 | artifactName: 'build-$(Agent.OS)' 37 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/sca/utils/TestParsedNode.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.utils; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | 5 | public class TestParsedNode implements IParsedNode { 6 | 7 | public String text = "SELECT * FROM 1"; 8 | public String className = TestParsedNode.class.getSimpleName(); 9 | 10 | public int index = 1; 11 | public int globalIndex = 2; 12 | 13 | @Override 14 | public int getIndex() { 15 | return index; 16 | } 17 | 18 | @Override 19 | public int getGlobalIndex() { 20 | return globalIndex; 21 | } 22 | 23 | @Override 24 | public String getClassName() { 25 | return className; 26 | } 27 | 28 | @Override 29 | public String getText() { 30 | return text; 31 | } 32 | 33 | @Override 34 | public int getLine() { 35 | return 0; 36 | } 37 | 38 | @Override 39 | public IParsedNode getControlFlowParent() { 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/1-tsql/README.md: -------------------------------------------------------------------------------- 1 | # TSQL project example # 2 | This folder contains example TSQL project and scripts allowing to setup required tools and illustrating usage of sonar-scanner with TSQL plugin. 3 | 4 | # Usage # 5 | 6 | Execute the following: 7 | 1. Update if needed and run *00-installTools.ps1*. It will download and extract to tools directory SqlCodeGuard, SonarScanner and SQLClover. 8 | 2. Update *01-executeDBChanges.ps1* with *server* name where to deploy tSQLt framework and run. 9 | 3. Publish database from src folder. 10 | 4. Update *server* and *sonarHost* parameters in *02-runSonar.ps1* file and run that script. 11 | 12 | TSQL sample project should now be visible in SonarQube server with coverage results. 13 | 14 | 15 | dotnet new --install MSBuild.Sdk.SqlProj.Templates 16 | 17 | # Execution 18 | 19 | 1. Update sonar-project.properties 20 | 2. Run **00-installTools.ps1**. This will download required tools 21 | 3. [Optional] Run 01-executeDBChanges.ps1 to deploy database 22 | 4. Update **02-runSonar.ps1** as required and run it. -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/SQLQualityProfile.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.dialects.SQLDialectRules; 5 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 6 | import org.sonar.plugins.sql.models.rules.Rule; 7 | import org.sonar.plugins.sql.models.rules.SqlRules; 8 | 9 | public class SQLQualityProfile implements BuiltInQualityProfilesDefinition { 10 | 11 | @Override 12 | public void define(Context context) { 13 | final NewBuiltInQualityProfile profile = 14 | context.createBuiltInQualityProfile("SQL rules", Constants.languageKey) 15 | .setDefault(true); 16 | final List rules = SQLDialectRules.INSTANCE.getGroupedRules(); 17 | 18 | for (SqlRules sqlRules : rules) { 19 | for (Rule rule : sqlRules.getRule()) { 20 | profile.activateRule(sqlRules.getRepoKey(), rule.getKey()); 21 | } 22 | } 23 | 24 | profile.done(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/nodes/IParsedNode.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.nodes; 2 | 3 | public interface IParsedNode { 4 | public String getClassName(); 5 | 6 | public String getText(); 7 | 8 | public int getLine(); 9 | 10 | public default IParsedNode[] getParents() { 11 | return new IParsedNode[0]; 12 | } 13 | 14 | public default IParsedNode[] getSiblings() { 15 | return new IParsedNode[0]; 16 | } 17 | 18 | public IParsedNode getControlFlowParent(); 19 | 20 | public default IParsedNode[] getUses() { 21 | return new IParsedNode[0]; 22 | } 23 | 24 | public default IParsedNode[] getChildren() { 25 | return new IParsedNode[0]; 26 | } 27 | 28 | public default int getDistance() { 29 | return 0; 30 | } 31 | 32 | public default int getIndex() { 33 | return 0; 34 | } 35 | 36 | public default int getIndex2() { 37 | return 0; 38 | } 39 | 40 | public default int getGlobalIndex() { 41 | return 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/issues/SqlIssuesList.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.issues; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | public class SqlIssuesList { 10 | private final Map> issues = new HashMap<>(); 11 | 12 | public Map> getIssues() { 13 | return issues; 14 | } 15 | 16 | public Collection getaLLIssues() { 17 | Set issues = new HashSet<>(); 18 | this.issues.forEach( 19 | (k, v) -> { 20 | issues.addAll(v); 21 | }); 22 | return issues; 23 | } 24 | 25 | public SqlIssuesList addIssue(final SqlIssue issue) { 26 | final Set issues = 27 | this.issues.getOrDefault(issue.getFileName(), new HashSet()); 28 | issues.add(issue); 29 | this.issues.put(issue.getFileName(), issues); 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/visitors/ClassTypesCountingVisitor.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.visitors; 2 | 3 | import java.util.Set; 4 | import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; 5 | import org.antlr.v4.runtime.tree.ParseTree; 6 | 7 | public class ClassTypesCountingVisitor extends AbstractParseTreeVisitor { 8 | 9 | private int counter = 0; 10 | 11 | public int getCounter() { 12 | return counter; 13 | } 14 | 15 | private final Set> classesToMatch; 16 | 17 | public ClassTypesCountingVisitor(final Set> classesToMatch) { 18 | this.classesToMatch = classesToMatch; 19 | } 20 | 21 | @Override 22 | public Void visit(final ParseTree tree) { 23 | 24 | final int n = tree.getChildCount(); 25 | 26 | if (classesToMatch.contains(tree.getClass())) { 27 | this.counter++; 28 | } 29 | 30 | for (int i = 0; i < n; i++) { 31 | final ParseTree c = tree.getChild(i); 32 | visit(c); 33 | } 34 | 35 | return defaultResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/1-tsql/01-executeDBChanges.ps1: -------------------------------------------------------------------------------- 1 | Add-Type -AssemblyName System.IO.Compression.FileSystem 2 | function Uzip-File { 3 | param( [string]$ziparchive, [string]$extractpath ) 4 | [System.IO.Compression.ZipFile]::ExtractToDirectory( $ziparchive, $extractpath ) 5 | } 6 | 7 | $baseToolsDir = "$PSScriptRoot\tools"; 8 | $tsqltDir = "$PSScriptRoot\tools\TSQLT" 9 | 10 | 11 | $server = ".\SQLEXPRESS01" 12 | $database = "ExampleDatabase" 13 | 14 | function Setup-TSQLT { 15 | $url = "https://tsqlt.org/download/tsqlt/"; 16 | $output = "$baseToolsDir\tsqlt.zip"; 17 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 18 | $WebClient = New-Object System.Net.WebClient 19 | $WebClient.DownloadFile($url,$output) 20 | Uzip-File -ziparchive $output -extractpath $tsqltDir 21 | } 22 | 23 | Setup-TSQLT 24 | 25 | &sqlcmd -S $server -i "$tsqltDir\SetClrEnabled.sql" 26 | &sqlcmd -S $server -Q "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'clr strict security', 0; RECONFIGURE;" 27 | &sqlcmd -S $server -Q "CREATE DATABASE $database;" 28 | &sqlcmd -S $server -d $database -i "$tsqltDir\tSQLt.class.sql" 29 | return -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase/BillingTests/test that customer with more than 3 orders and total over 2000 can discount.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE PROCEDURE [BillingTests].[test that customer with more than 3 orders and total over 2000 can discount] 3 | AS 4 | BEGIN 5 | --Assemble: Fake the Particle table to make sure it is empty and that constraints will not be a problem 6 | EXEC tSQLt.FakeTable 'Billing.Customers'; 7 | EXEC tSQLt.FakeTable 'Billing.Orders'; 8 | -- Populate the Particle table with rows that hug the rectangle boundaries 9 | INSERT INTO Billing.Customers VALUES ( 1, 'test'); 10 | INSERT INTO Billing.Orders VALUES ('order1', 1, 1000, DATEADD(day,-1,GETDATE())); 11 | INSERT INTO Billing.Orders VALUES ('order2', 1, 2000, DATEADD(day,-1,GETDATE())); 12 | INSERT INTO Billing.Orders VALUES ('order3', 1, 3330, DATEADD(day,-1,GETDATE())); 13 | INSERT INTO Billing.Orders VALUES ('order4', 1, 12220, DATEADD(day,-1,GETDATE())); 14 | DECLARE @actualComputedResult BIT; 15 | SET @actualComputedResult = Billing.IsCustomerEligibleForDiscount(1); 16 | EXEC tSQLt.AssertEquals 1, @actualComputedResult; 17 | END; -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | // idea from https://github.com/microsoft/vscode-dev-containers/blob/main/containers/java/.devcontainer/devcontainer.json 3 | "name": "SonarQube plugin", 4 | "build": { 5 | "dockerfile": "Dockerfile", 6 | "args": { 7 | "VARIANT": "16", 8 | "MAVEN_VERSION": "3.8.1", 9 | "INSTALL_MAVEN": "true", 10 | "INSTALL_GRADLE": "false", 11 | "GRADLE_VERSION": "" 12 | } 13 | }, 14 | 15 | // Set *default* container specific settings.json values on container create. 16 | "settings": { 17 | "java.home": "/docker-java-home" 18 | }, 19 | 20 | // Add the IDs of extensions you want installed when the container is created. 21 | "extensions": [ 22 | "vscjava.vscode-java-pack" 23 | ], 24 | 25 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 26 | // "forwardPorts": [], 27 | 28 | // Use 'postCreateCommand' to run commands after the container is created. 29 | // "postCreateCommand": "java -version", 30 | 31 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 32 | "remoteUser": "vscode" 33 | } -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammar.tokens: -------------------------------------------------------------------------------- 1 | WS=1 2 | COMMENT=2 3 | LINE_COMMENT=3 4 | DOUBLE_QUOTE_ID=4 5 | SINGLE_QUOTE=5 6 | SQUARE_BRACKET_ID=6 7 | LOCAL_ID=7 8 | DECIMAL=8 9 | ID=9 10 | STRING=10 11 | BINARY=11 12 | FLOAT=12 13 | REAL=13 14 | EQUAL=14 15 | GREATER=15 16 | LESS=16 17 | EXCLAMATION=17 18 | PLUS_ASSIGN=18 19 | MINUS_ASSIGN=19 20 | MULT_ASSIGN=20 21 | DIV_ASSIGN=21 22 | MOD_ASSIGN=22 23 | AND_ASSIGN=23 24 | XOR_ASSIGN=24 25 | OR_ASSIGN=25 26 | DOUBLE_BAR=26 27 | DOT=27 28 | UNDERLINE=28 29 | AT=29 30 | SHARP=30 31 | DOLLAR=31 32 | LR_BRACKET=32 33 | RR_BRACKET=33 34 | COMMA=34 35 | SEMI=35 36 | COLON=36 37 | DOUBLE_COLON=37 38 | STAR=38 39 | DIVIDE=39 40 | MODULE=40 41 | PLUS=41 42 | MINUS=42 43 | BIT_NOT=43 44 | BIT_OR=44 45 | BIT_AND=45 46 | BIT_XOR=46 47 | '\''=5 48 | '='=14 49 | '>'=15 50 | '<'=16 51 | '!'=17 52 | '+='=18 53 | '-='=19 54 | '*='=20 55 | '/='=21 56 | '%='=22 57 | '&='=23 58 | '^='=24 59 | '|='=25 60 | '||'=26 61 | '.'=27 62 | '_'=28 63 | '@'=29 64 | '#'=30 65 | '$'=31 66 | '('=32 67 | ')'=33 68 | ','=34 69 | ';'=35 70 | ':'=36 71 | '::'=37 72 | '*'=38 73 | '/'=39 74 | '%'=40 75 | '+'=41 76 | '-'=42 77 | '~'=43 78 | '|'=44 79 | '&'=45 80 | '^'=46 81 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammarLexer.tokens: -------------------------------------------------------------------------------- 1 | WS=1 2 | COMMENT=2 3 | LINE_COMMENT=3 4 | DOUBLE_QUOTE_ID=4 5 | SINGLE_QUOTE=5 6 | SQUARE_BRACKET_ID=6 7 | LOCAL_ID=7 8 | DECIMAL=8 9 | ID=9 10 | STRING=10 11 | BINARY=11 12 | FLOAT=12 13 | REAL=13 14 | EQUAL=14 15 | GREATER=15 16 | LESS=16 17 | EXCLAMATION=17 18 | PLUS_ASSIGN=18 19 | MINUS_ASSIGN=19 20 | MULT_ASSIGN=20 21 | DIV_ASSIGN=21 22 | MOD_ASSIGN=22 23 | AND_ASSIGN=23 24 | XOR_ASSIGN=24 25 | OR_ASSIGN=25 26 | DOUBLE_BAR=26 27 | DOT=27 28 | UNDERLINE=28 29 | AT=29 30 | SHARP=30 31 | DOLLAR=31 32 | LR_BRACKET=32 33 | RR_BRACKET=33 34 | COMMA=34 35 | SEMI=35 36 | COLON=36 37 | DOUBLE_COLON=37 38 | STAR=38 39 | DIVIDE=39 40 | MODULE=40 41 | PLUS=41 42 | MINUS=42 43 | BIT_NOT=43 44 | BIT_OR=44 45 | BIT_AND=45 46 | BIT_XOR=46 47 | '\''=5 48 | '='=14 49 | '>'=15 50 | '<'=16 51 | '!'=17 52 | '+='=18 53 | '-='=19 54 | '*='=20 55 | '/='=21 56 | '%='=22 57 | '&='=23 58 | '^='=24 59 | '|='=25 60 | '||'=26 61 | '.'=27 62 | '_'=28 63 | '@'=29 64 | '#'=30 65 | '$'=31 66 | '('=32 67 | ')'=33 68 | ','=34 69 | ';'=35 70 | ':'=36 71 | '::'=37 72 | '*'=38 73 | '/'=39 74 | '%'=40 75 | '+'=41 76 | '-'=42 77 | '~'=43 78 | '|'=44 79 | '&'=45 80 | '^'=46 81 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/DistanceMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | import org.sonar.plugins.sql.models.rules.RuleDistanceIndexMatchType; 5 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 6 | 7 | public class DistanceMatcher implements IMatcher { 8 | 9 | @Override 10 | public boolean match(IParsedNode item, RuleImplementation rule) { 11 | if (rule.getDistance() == 0) { 12 | return true; 13 | } 14 | int val = item.getDistance(); 15 | if (rule.getDistanceCheckType() == RuleDistanceIndexMatchType.LESS) { 16 | if (val > rule.getDistance()) { 17 | return false; 18 | } 19 | } 20 | if (rule.getDistanceCheckType() == RuleDistanceIndexMatchType.MORE) { 21 | if (val < rule.getDistance()) { 22 | return false; 23 | } 24 | } 25 | if (rule.getDistanceCheckType() == RuleDistanceIndexMatchType.EQUALS) { 26 | if (rule.getDistance() != val) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/sca/matchers/IndexMatcherTest.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.antlr.sql.sca.utils.TestParsedNode; 6 | import org.junit.Test; 7 | import org.sonar.plugins.sql.models.rules.RuleDistanceIndexMatchType; 8 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 9 | 10 | public class IndexMatcherTest { 11 | 12 | private final IndexMatcher sut = new IndexMatcher(); 13 | 14 | @Test 15 | public void testRuleHasZeroIndex() { 16 | TestParsedNode node = new TestParsedNode(); 17 | RuleImplementation rule = new RuleImplementation(); 18 | 19 | boolean result = sut.match(node, rule); 20 | 21 | assertThat(result).isTrue(); 22 | } 23 | 24 | @Test 25 | public void testRuleHasNoneZeroIndexAndExpectsBiggerIndex() { 26 | TestParsedNode node = new TestParsedNode(); 27 | RuleImplementation rule = new RuleImplementation(); 28 | rule.setIndex(20); 29 | rule.setIndexCheckType(RuleDistanceIndexMatchType.MORE); 30 | 31 | boolean result = sut.match(node, rule); 32 | 33 | assertThat(result).isFalse(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.devcontainer/dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Java version: 11, 16 2 | ARG VARIANT=11 3 | FROM mcr.microsoft.com/vscode/devcontainers/java:${VARIANT} 4 | 5 | # [Option] Install Maven 6 | ARG INSTALL_MAVEN="false" 7 | ARG MAVEN_VERSION="" 8 | # [Option] Install Gradle 9 | ARG INSTALL_GRADLE="false" 10 | ARG GRADLE_VERSION="" 11 | RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ 12 | && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi 13 | 14 | # [Option] Install Node.js 15 | ARG INSTALL_NODE="true" 16 | ARG NODE_VERSION="lts/*" 17 | RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 18 | 19 | # [Optional] Uncomment this section to install additional OS packages. 20 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 21 | # && apt-get -y install --no-install-recommends 22 | 23 | # [Optional] Uncomment this line to install global node packages. 24 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/BaseDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.models.AntlrContext; 4 | import org.antlr.v4.runtime.CharStream; 5 | import org.antlr.v4.runtime.CharStreams; 6 | import org.antlr.v4.runtime.CommonTokenStream; 7 | import org.antlr.v4.runtime.Lexer; 8 | import org.antlr.v4.runtime.tree.ParseTree; 9 | import org.sonar.plugins.sql.CaseChangingCharStream; 10 | 11 | public abstract class BaseDialect implements IDialect { 12 | 13 | protected abstract Lexer getLexer(CharStream charStream); 14 | 15 | protected abstract ParseTree getRoot(CommonTokenStream stream); 16 | 17 | protected abstract DialectLanguageTypesMap getTypesMap(); 18 | 19 | public AntlrContext parse(String text) { 20 | final CharStream charStream = 21 | new CaseChangingCharStream(CharStreams.fromString(text), true); 22 | 23 | Lexer lexer = this.getLexer(charStream); 24 | lexer.removeErrorListeners(); 25 | 26 | final CommonTokenStream stream = new CommonTokenStream(lexer); 27 | stream.fill(); 28 | 29 | ParseTree tree = this.getRoot(stream); 30 | 31 | AntlrContext c = new AntlrContext(); 32 | 33 | c.root = tree; 34 | c.stream = stream; 35 | c.types = this.getTypesMap(); 36 | c.lexer = lexer; 37 | 38 | return c; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/1-tsql/src/ExampleDatabase/ExampleDatabase.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "ExampleDatabase", "ExampleDatabase\ExampleDatabase.sqlproj", "{1EE51303-9E3E-49F4-BCAC-92E9F5758931}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 17 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {1EE51303-9E3E-49F4-BCAC-92E9F5758931}.Release|Any CPU.Deploy.0 = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ExtensibilityGlobals) = postSolution 25 | SolutionGuid = {5391EDB2-010B-469F-8931-BD24084B38FF} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /src/external/Readme.md: -------------------------------------------------------------------------------- 1 | Grammars used: 2 | 3 | - https://github.com/tshprecher/antlr_psql 4 | - https://github.com/antlr/grammars-v4 5 | - https://github.com/gretard/antlr4-grammar-vsql 6 | 7 | Build: 8 | 9 | - git clone https://github.com/antlr/grammars-v4.git 10 | - git clone https://github.com/tshprecher/antlr_psql.git 11 | - git clone https://github.com/gretard/antlr4-grammar-vsql.git 12 | - cd grammars-v4 && git pull && cd .. 13 | - cd antlr_psql && git pull && cd .. 14 | - cd antlr4-grammar-vsql && git pull && cd .. 15 | - mvn antlr4:antlr4 -Dgrammars="grammars-v4/sql/mysql/Positive-Technologies" -Dgrammars.package="org.antlr.sql.dialects.mysql" -Dgrammars.out.dir="mysql" 16 | - mvn antlr4:antlr4 -Dgrammars="grammars-v4/sql/tsql" -Dgrammars.package="org.antlr.sql.dialects.tsql" -Dgrammars.out.dir="tsql" 17 | - mvn antlr4:antlr4 -Dgrammars="grammars-v4/sql/postgresql" -Dgrammars.package="org.antlr.sql.dialects.psqlv2" -Dgrammars.out.dir="psqlv2" 18 | - mvn antlr4:antlr4 -Dgrammars="grammars-v4/sql/snowflake" -Dgrammars.package="org.antlr.sql.dialects.snowflake" -Dgrammars.out.dir="snowflake" 19 | - mvn antlr4:antlr4 -Dgrammars="antlr_psql/antlr4" -Dgrammars.package="org.antlr.sql.dialects.psqlv1" -Dgrammars.out.dir="psqlv1" 20 | - mvn antlr4:antlr4 -Dgrammars="antlr4-grammar-vsql/antlr4-grammar-vsql/src/main/antlr4" -Dgrammars.package="org.antlr.sql.dialects.vsql" -Dgrammars.out.dir="vsql" 21 | - mvn antlr4:antlr4 -Dgrammars="antlr4-grammar-sql-comments/src/main/antlr4" -Dgrammars.package="org.antlr.sql.dialects.comments" -Dgrammars.out.dir="comments" 22 | 23 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/DialectLanguageTypesMap.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class DialectLanguageTypesMap { 7 | 8 | private final Set> cyclomaticComplexity = new HashSet<>(); 9 | 10 | private final Set> cognitiveComplexity = new HashSet<>(); 11 | 12 | private final Set commentTokens = new HashSet<>(); 13 | 14 | private final Set stringTokens = new HashSet<>(); 15 | 16 | public Set> getCyclomaticComplexityClasses() { 17 | return cyclomaticComplexity; 18 | } 19 | 20 | public Set> getCognitiveComplexityClasses() { 21 | return cognitiveComplexity; 22 | } 23 | 24 | public Set getCommentTokens() { 25 | return commentTokens; 26 | } 27 | 28 | public Set getStringTokens() { 29 | return stringTokens; 30 | } 31 | 32 | public DialectLanguageTypesMap addCommentToken(int e) { 33 | commentTokens.add(e); 34 | return this; 35 | } 36 | 37 | public DialectLanguageTypesMap addStringToken(int e) { 38 | stringTokens.add(e); 39 | return this; 40 | } 41 | 42 | public DialectLanguageTypesMap addComplexityType(Class e) { 43 | cyclomaticComplexity.add(e); 44 | return this; 45 | } 46 | 47 | public DialectLanguageTypesMap addCognitiveComplexityType(Class e) { 48 | cognitiveComplexity.add(e); 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/IssuesFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import org.antlr.sql.models.AntlrContext; 6 | import org.antlr.sql.sca.IssuesProvider; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.utils.log.Logger; 10 | import org.sonar.api.utils.log.Loggers; 11 | import org.sonar.plugins.sql.issues.RuleToCheck; 12 | import org.sonar.plugins.sql.issues.SqlIssuesList; 13 | import org.sonar.plugins.sql.models.rules.SqlRules; 14 | import org.sonar.plugins.sql.sensors.BaseSensor; 15 | 16 | public class IssuesFiller extends BaseSensor implements Filler { 17 | private static final Logger LOGGER = Loggers.get(IssuesFiller.class); 18 | private final IssuesProvider issuesProvider = new IssuesProvider(); 19 | 20 | @Override 21 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 22 | SqlIssuesList sqlIssuesList = getIssues(antlrContext); 23 | 24 | try { 25 | addIssues(context, sqlIssuesList, file); 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | 31 | public SqlIssuesList getIssues(AntlrContext antlrContext) { 32 | List rulesToCheck = 33 | RuleToCheck.createCodeList2(antlrContext.rules.toArray(new SqlRules[0])); 34 | return issuesProvider.check(rulesToCheck, antlrContext.root); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/tsql/rules1.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | CC101 6 | nolock hint is not being used 7 | CC101 8 | HTML 9 | nolock hint is not being used 10 | BLOCKER 11 | SINGLE 12 | 13 | 14 | Select_statementContext 15 | 16 | 17 | 18 | 19 | Table_hintContext 20 | 21 | 22 | NOLOCk 23 | 24 | TextAndClass 25 | FailIfNotFound 26 | Default 27 | Default 28 | 29 | 30 | Nolock is not being used 31 | ClassOnly 32 | 33 | SELECT * from dbo.test; 34 | 35 | 36 | SELECT * from dbo.test WITH (NOLOCK); 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/CyclomaticComplexityFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import org.antlr.sql.dialects.DialectLanguageTypesMap; 4 | import org.antlr.sql.models.AntlrContext; 5 | import org.antlr.sql.visitors.ClassTypesCountingVisitor; 6 | import org.antlr.v4.runtime.tree.ParseTree; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.measures.CoreMetrics; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | 13 | public class CyclomaticComplexityFiller implements Filler { 14 | private static final Logger LOGGER = Loggers.get(CyclomaticComplexityFiller.class); 15 | 16 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 17 | try { 18 | 19 | DialectLanguageTypesMap types = antlrContext.types; 20 | ParseTree root = antlrContext.getRoot(); 21 | 22 | ClassTypesCountingVisitor visitor = 23 | new ClassTypesCountingVisitor(types.getCyclomaticComplexityClasses()); 24 | 25 | visitor.visit(root); 26 | 27 | synchronized (context) { 28 | context.newMeasure() 29 | .on(file) 30 | .forMetric(CoreMetrics.COMPLEXITY) 31 | .withValue(visitor.getCounter() + 1) 32 | .save(); 33 | } 34 | 35 | } catch (Throwable e) { 36 | LOGGER.warn("Error adding complexity measures on " + file, e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/CognitiveComplexityFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import org.antlr.sql.dialects.DialectLanguageTypesMap; 4 | import org.antlr.sql.models.AntlrContext; 5 | import org.antlr.sql.visitors.ClassTypesCountingVisitor; 6 | import org.antlr.v4.runtime.tree.ParseTree; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.measures.CoreMetrics; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | 13 | public class CognitiveComplexityFiller implements Filler { 14 | 15 | private static final Logger LOGGER = Loggers.get(CognitiveComplexityFiller.class); 16 | 17 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 18 | try { 19 | DialectLanguageTypesMap types = antlrContext.types; 20 | ParseTree root = antlrContext.getRoot(); 21 | 22 | ClassTypesCountingVisitor visitor = 23 | new ClassTypesCountingVisitor(types.getCognitiveComplexityClasses()); 24 | 25 | visitor.visit(root); 26 | 27 | synchronized (context) { 28 | context.newMeasure() 29 | .on(file) 30 | .forMetric(CoreMetrics.COGNITIVE_COMPLEXITY) 31 | .withValue(visitor.getCounter() + 1) 32 | .save(); 33 | } 34 | 35 | } catch (Throwable e) { 36 | LOGGER.warn("Error adding cognitive complexity measures on " + file, e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/MySqlDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.mysql.MySqlLexer; 4 | import org.antlr.sql.dialects.mysql.MySqlParser; 5 | import org.antlr.sql.dialects.mysql.MySqlParser.DmlStatementContext; 6 | import org.antlr.sql.dialects.mysql.MySqlParser.PredicateExpressionContext; 7 | import org.antlr.sql.dialects.mysql.MySqlParser.ScalarFunctionCallContext; 8 | import org.antlr.v4.runtime.CharStream; 9 | import org.antlr.v4.runtime.CommonTokenStream; 10 | import org.antlr.v4.runtime.Lexer; 11 | import org.antlr.v4.runtime.tree.ParseTree; 12 | 13 | public class MySqlDialect extends BaseDialect { 14 | 15 | @Override 16 | protected Lexer getLexer(CharStream charStream) { 17 | return new MySqlLexer(charStream); 18 | } 19 | 20 | @Override 21 | protected ParseTree getRoot(CommonTokenStream stream) { 22 | MySqlParser p = new MySqlParser(stream); 23 | p.removeErrorListeners(); 24 | return p.root(); 25 | } 26 | 27 | @Override 28 | protected DialectLanguageTypesMap getTypesMap() { 29 | return new DialectLanguageTypesMap() 30 | .addCommentToken(MySqlParser.COMMENT) 31 | .addCommentToken(MySqlParser.LINE_COMMENT) 32 | .addStringToken(MySqlParser.STRING) 33 | .addCognitiveComplexityType(PredicateExpressionContext.class) 34 | .addCognitiveComplexityType(DmlStatementContext.class) 35 | .addCognitiveComplexityType(ScalarFunctionCallContext.class) 36 | .addComplexityType(PredicateExpressionContext.class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/CaseChangingCharStream.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import org.antlr.v4.runtime.CharStream; 4 | import org.antlr.v4.runtime.misc.Interval; 5 | 6 | public class CaseChangingCharStream implements CharStream { 7 | 8 | private final CharStream stream; 9 | private final boolean upper; 10 | 11 | public CaseChangingCharStream(final CharStream stream, final boolean upper) { 12 | this.stream = stream; 13 | this.upper = upper; 14 | } 15 | 16 | @Override 17 | public String getText(Interval interval) { 18 | return stream.getText(interval); 19 | } 20 | 21 | @Override 22 | public void consume() { 23 | stream.consume(); 24 | } 25 | 26 | @Override 27 | public int LA(int i) { 28 | int c = stream.LA(i); 29 | if (c <= 0) { 30 | return c; 31 | } 32 | if (upper) { 33 | return Character.toUpperCase(c); 34 | } 35 | return Character.toLowerCase(c); 36 | } 37 | 38 | @Override 39 | public int mark() { 40 | return stream.mark(); 41 | } 42 | 43 | @Override 44 | public void release(int marker) { 45 | stream.release(marker); 46 | } 47 | 48 | @Override 49 | public int index() { 50 | return stream.index(); 51 | } 52 | 53 | @Override 54 | public void seek(int index) { 55 | stream.seek(index); 56 | } 57 | 58 | @Override 59 | public int size() { 60 | return stream.size(); 61 | } 62 | 63 | @Override 64 | public String getSourceName() { 65 | return stream.getSourceName(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/VSQLDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.vsql.VSqlLexer; 4 | import org.antlr.sql.dialects.vsql.VSqlParser; 5 | import org.antlr.v4.runtime.CharStream; 6 | import org.antlr.v4.runtime.CommonTokenStream; 7 | import org.antlr.v4.runtime.Lexer; 8 | import org.antlr.v4.runtime.tree.ParseTree; 9 | 10 | public class VSQLDialect extends BaseDialect { 11 | 12 | @Override 13 | protected Lexer getLexer(CharStream charStream) { 14 | return new VSqlLexer(charStream); 15 | } 16 | 17 | @Override 18 | protected ParseTree getRoot(CommonTokenStream stream) { 19 | VSqlParser p = new VSqlParser(stream); 20 | p.removeErrorListeners(); 21 | return p.root(); 22 | } 23 | 24 | @Override 25 | protected DialectLanguageTypesMap getTypesMap() { 26 | return new DialectLanguageTypesMap() 27 | .addCommentToken(VSqlParser.COMMENT) 28 | .addCommentToken(VSqlParser.LINE_COMMENT) 29 | .addStringToken(VSqlParser.DOUBLE_QUOTE_STRING) 30 | .addStringToken(VSqlParser.SINGLE_QUOTE_STRING) 31 | .addComplexityType(VSqlParser.ExpressionContext.class) 32 | .addCognitiveComplexityType(VSqlParser.FunctionCallContext.class) 33 | .addCognitiveComplexityType(VSqlParser.JoinPredicateContext.class) 34 | .addCognitiveComplexityType(VSqlParser.Orderby_clauseContext.class) 35 | .addCognitiveComplexityType(VSqlParser.Where_clauseContext.class) 36 | .addCognitiveComplexityType(VSqlParser.StatementContext.class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/TSQLDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.tsql.TSqlLexer; 4 | import org.antlr.sql.dialects.tsql.TSqlParser; 5 | import org.antlr.v4.runtime.CharStream; 6 | import org.antlr.v4.runtime.CommonTokenStream; 7 | import org.antlr.v4.runtime.Lexer; 8 | import org.antlr.v4.runtime.tree.ParseTree; 9 | 10 | public class TSQLDialect extends BaseDialect { 11 | 12 | @Override 13 | protected Lexer getLexer(CharStream charStream) { 14 | return new TSqlLexer(charStream); 15 | } 16 | 17 | @Override 18 | protected ParseTree getRoot(CommonTokenStream stream) { 19 | TSqlParser p = new TSqlParser(stream); 20 | p.removeErrorListeners(); 21 | return p.tsql_file(); 22 | } 23 | 24 | @Override 25 | protected DialectLanguageTypesMap getTypesMap() { 26 | return new DialectLanguageTypesMap() 27 | .addCommentToken(TSqlParser.COMMENT) 28 | .addCommentToken(TSqlParser.LINE_COMMENT) 29 | .addStringToken(TSqlParser.STRING) 30 | .addComplexityType(TSqlParser.Search_conditionContext.class) 31 | .addComplexityType(TSqlParser.Try_catch_statementContext.class) 32 | .addCognitiveComplexityType(TSqlParser.Sql_unionContext.class) 33 | .addCognitiveComplexityType(TSqlParser.Function_callContext.class) 34 | .addCognitiveComplexityType(TSqlParser.Join_partContext.class) 35 | .addCognitiveComplexityType(TSqlParser.Order_by_expressionContext.class) 36 | .addCognitiveComplexityType(TSqlParser.Search_conditionContext.class) 37 | .addCognitiveComplexityType(TSqlParser.Dml_clauseContext.class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/6-pssql-with-custom-rules/rules/codeCustomRules.customRules: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | C001 5 | Multiline comments missing 6 | Test rule to detect missing multile comments 7 | 8 | 9 | Multiline_commentContext 10 | 11 | FailIfNotFound 12 | File does not have any comments 13 | 14 | CODE_SMELL 15 | comments 16 | file 17 | 18 | 19 | 20 | R001 21 | Random order is used 22 | Result order will not be consistent as call to RANDOM() function was found in ORDER clause 23 | 24 | 25 | Sort_clauseContext 26 | 27 | 28 | 29 | 30 | Builtin_function_nameContext 31 | 32 | 33 | RANDOM 34 | 35 | TextAndClass 36 | FailIfFound 37 | 38 | 39 | SkipIfNotFound 40 | Call to RANDOM() function was found in ORDER clause 41 | 42 | CODE_SMELL 43 | code 44 | line 45 | 46 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/TextMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.sca.nodes.IParsedNode; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 7 | import org.sonar.plugins.sql.models.rules.RuleMatchType; 8 | 9 | public class TextMatcher implements IMatcher { 10 | 11 | @Override 12 | public boolean match(IParsedNode item, RuleImplementation ruleImplementation) { 13 | 14 | if (ruleImplementation.getRuleMatchType() == RuleMatchType.CLASS_ONLY) { 15 | return true; 16 | } 17 | final org.sonar.plugins.sql.models.rules.TextCheckType type = 18 | ruleImplementation.getTextCheckType(); 19 | 20 | final String text = item.getText(); 21 | final List names = ruleImplementation.getTextToFind().getTextItem(); 22 | if (names.isEmpty()) { 23 | return true; 24 | } 25 | 26 | for (final String searchItem : names) { 27 | switch (type) { 28 | case DEFAULT: 29 | case CONTAINS: 30 | if (StringUtils.containsIgnoreCase(text, searchItem)) { 31 | return true; 32 | } 33 | break; 34 | 35 | case REGEXP: 36 | if (text.matches(searchItem)) { 37 | return true; 38 | } 39 | break; 40 | case STRICT: 41 | if (text.equalsIgnoreCase(searchItem)) { 42 | return true; 43 | } 44 | break; 45 | default: 46 | break; 47 | } 48 | } 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/psqlv2/PostgreSQLLexerBase.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects.psqlv2; 2 | import java.util.ArrayDeque; 3 | import java.util.Deque; 4 | 5 | import org.antlr.v4.runtime.CharStream; 6 | import org.antlr.v4.runtime.Lexer; 7 | 8 | public abstract class PostgreSQLLexerBase extends Lexer { 9 | protected final Deque tags = new ArrayDeque<>(); 10 | 11 | protected PostgreSQLLexerBase(CharStream input) { 12 | super(input); 13 | 14 | } 15 | 16 | public void pushTag() { 17 | tags.push(getText()); 18 | } 19 | 20 | public boolean isTag() { 21 | return getText().equals(tags.peek()); 22 | } 23 | 24 | public void popTag() { 25 | tags.pop(); 26 | } 27 | 28 | public boolean checkLA(int c) { 29 | return getInputStream().LA(1) != c; 30 | } 31 | 32 | public boolean charIsLetter() { 33 | return Character.isLetter(getInputStream().LA(-1)); 34 | } 35 | 36 | public void HandleNumericFail() { 37 | getInputStream().seek(getInputStream().index() - 2); 38 | setType(PostgreSQLLexer.Integral); 39 | } 40 | 41 | public void HandleLessLessGreaterGreater() { 42 | if (getText() == "<<") setType(PostgreSQLLexer.LESS_LESS); 43 | if (getText() == ">>") setType(PostgreSQLLexer.GREATER_GREATER); 44 | } 45 | 46 | public void UnterminatedBlockCommentDebugAssert() { 47 | //Debug.Assert(InputStream.LA(1) == -1 /*EOF*/); 48 | } 49 | 50 | public boolean CheckIfUtf32Letter() { 51 | int codePoint = getInputStream().LA(-2) << 8 + getInputStream().LA(-1); 52 | char[] c; 53 | if (codePoint < 0x10000) { 54 | c = new char[]{(char) codePoint}; 55 | } else { 56 | codePoint -= 0x10000; 57 | c = new char[]{(char) (codePoint / 0x400 + 0xd800), (char) (codePoint % 0x400 + 0xdc00)}; 58 | } 59 | return Character.isLetter(c[0]); 60 | } 61 | } -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/sca/PSQLV2DialectTest.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import org.antlr.sql.dialects.Dialects; 6 | import org.antlr.sql.models.AntlrContext; 7 | import org.antlr.v4.runtime.Token; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class PSQLV2DialectTest { 12 | 13 | @Test 14 | public void testComments() throws IOException { 15 | 16 | AntlrContext antlrContext = parse("/** test**/\r\n---test"); 17 | 18 | Assert.assertNotNull(antlrContext); 19 | 20 | List tokens = antlrContext.getAllTokens(); 21 | Assert.assertFalse(tokens.isEmpty()); 22 | Assert.assertTrue(antlrContext.isComment(tokens.get(0))); 23 | Assert.assertTrue(antlrContext.isComment(tokens.get(2))); 24 | } 25 | 26 | @Test 27 | public void testKeyword() throws IOException { 28 | 29 | AntlrContext antlrContext = parse("select 1"); 30 | 31 | Assert.assertNotNull(antlrContext); 32 | 33 | List tokens = antlrContext.getAllTokens(); 34 | Assert.assertFalse(tokens.isEmpty()); 35 | Assert.assertTrue(antlrContext.isKeyword(tokens.get(0))); 36 | } 37 | 38 | @Test 39 | public void testString() throws IOException { 40 | 41 | AntlrContext antlrContext = parse("select 'aa'"); 42 | 43 | Assert.assertNotNull(antlrContext); 44 | 45 | List tokens = antlrContext.getAllTokens(); 46 | Assert.assertFalse(tokens.isEmpty()); 47 | Assert.assertTrue(antlrContext.isString(tokens.get(2))); 48 | } 49 | 50 | private static AntlrContext parse(String sql) { 51 | AntlrContext antlrContext = Dialects.PSSQLV2.parse(sql); 52 | 53 | // PrettyPrinter.print(antlrContext.root, 0, antlrContext.stream); 54 | // PrettyPrinter.printTokens(antlrContext); 55 | return antlrContext; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammarVisitor.java: -------------------------------------------------------------------------------- 1 | // Generated from java-escape by ANTLR 4.11.1 2 | package org.antlr.sql.dialects.comments; 3 | import org.antlr.v4.runtime.tree.ParseTreeVisitor; 4 | 5 | /** 6 | * This interface defines a complete generic visitor for a parse tree produced 7 | * by {@link CommentsGrammarParser}. 8 | * 9 | * @param The return type of the visit operation. Use {@link Void} for 10 | * operations with no return type. 11 | */ 12 | public interface CommentsGrammarVisitor extends ParseTreeVisitor { 13 | /** 14 | * Visit a parse tree produced by {@link CommentsGrammarParser#root}. 15 | * @param ctx the parse tree 16 | * @return the visitor result 17 | */ 18 | T visitRoot(CommentsGrammarParser.RootContext ctx); 19 | /** 20 | * Visit a parse tree produced by {@link CommentsGrammarParser#statement}. 21 | * @param ctx the parse tree 22 | * @return the visitor result 23 | */ 24 | T visitStatement(CommentsGrammarParser.StatementContext ctx); 25 | /** 26 | * Visit a parse tree produced by {@link CommentsGrammarParser#comment}. 27 | * @param ctx the parse tree 28 | * @return the visitor result 29 | */ 30 | T visitComment(CommentsGrammarParser.CommentContext ctx); 31 | /** 32 | * Visit a parse tree produced by {@link CommentsGrammarParser#multiline_comment}. 33 | * @param ctx the parse tree 34 | * @return the visitor result 35 | */ 36 | T visitMultiline_comment(CommentsGrammarParser.Multiline_commentContext ctx); 37 | /** 38 | * Visit a parse tree produced by {@link CommentsGrammarParser#line_comment}. 39 | * @param ctx the parse tree 40 | * @return the visitor result 41 | */ 42 | T visitLine_comment(CommentsGrammarParser.Line_commentContext ctx); 43 | /** 44 | * Visit a parse tree produced by {@link CommentsGrammarParser#non_comment}. 45 | * @param ctx the parse tree 46 | * @return the visitor result 47 | */ 48 | T visitNon_comment(CommentsGrammarParser.Non_commentContext ctx); 49 | } -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/RuleMode.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import javax.xml.bind.annotation.XmlEnum; 12 | import javax.xml.bind.annotation.XmlEnumValue; 13 | import javax.xml.bind.annotation.XmlType; 14 | import org.codehaus.plexus.util.StringUtils; 15 | 16 | /** 17 | * Java class for ruleMode. 18 | * 19 | *

The following schema fragment specifies the expected content contained within this class. 20 | * 21 | *

22 | * 23 | *

24 |  * <simpleType name="ruleMode">
25 |  *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
26 |  *     <enumeration value="Default"/>
27 |  *     <enumeration value="Group"/>
28 |  *     <enumeration value="Single"/>
29 |  *   </restriction>
30 |  * </simpleType>
31 |  * 
32 | */ 33 | @XmlType(name = "ruleMode") 34 | @XmlEnum 35 | public enum RuleMode { 36 | @XmlEnumValue("Default") 37 | DEFAULT("Default"), 38 | @XmlEnumValue("Group") 39 | GROUP("Group"), 40 | @XmlEnumValue("Single") 41 | SINGLE("Single"); 42 | private final String value; 43 | 44 | RuleMode(String v) { 45 | value = v; 46 | } 47 | 48 | public String value() { 49 | return value; 50 | } 51 | 52 | public static RuleMode fromValue(String v) { 53 | 54 | if (StringUtils.isBlank(v)) { 55 | return RuleMode.DEFAULT; 56 | } 57 | for (RuleMode c : RuleMode.values()) { 58 | if (c.value.equalsIgnoreCase(v)) { 59 | return c; 60 | } 61 | } 62 | throw new IllegalArgumentException(v); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/1-tsql/00-installTools.ps1: -------------------------------------------------------------------------------- 1 | Add-Type -AssemblyName System.IO.Compression.FileSystem 2 | 3 | function Uzip-File { 4 | param( [string]$ziparchive, [string]$extractpath ) 5 | [System.IO.Compression.ZipFile]::ExtractToDirectory( $ziparchive, $extractpath ) 6 | } 7 | 8 | $baseToolsDir = "$PSScriptRoot\tools"; 9 | New-Item -ItemType Directory -Force -Path $baseToolsDir 10 | 11 | function Setup-SqlCodeGuard { 12 | $url = "http://download.red-gate.com/SQLCodeGuardCmdLine.zip" 13 | $output = "$baseToolsDir\SQLCodeGuardCmdLine.zip"; 14 | $outputDir = "$baseToolsDir\SQLCodeGuardCmdLine" 15 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 16 | $WebClient = New-Object System.Net.WebClient 17 | $WebClient.DownloadFile($url,$output) 18 | Uzip-File -ziparchive $output -extractpath $outputDir 19 | Write-output "Extracted SQLCodeGuardCmdLine to $outputDir" 20 | } 21 | 22 | function Setup-SonarScanner { 23 | $url = "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472-windows.zip" 24 | $output = "$baseToolsDir\scanner.zip"; 25 | $outputDir = "$baseToolsDir\sonar-scanner" 26 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 27 | $WebClient = New-Object System.Net.WebClient 28 | $WebClient.DownloadFile($url,$output) 29 | Uzip-File -ziparchive $output -extractpath $outputDir 30 | Write-output "Extracted sonar-scanner to $outputDir" 31 | } 32 | 33 | function Setup-SQLCover { 34 | $url = "https://github.com/GoEddie/SQLCover/releases/download/0.4.1/SQLCOver.0.4.1.zip"; 35 | $output = "$baseToolsDir\sqlCover.zip"; 36 | $outputDir = "$baseToolsDir\SQLCover" 37 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 38 | $WebClient = New-Object System.Net.WebClient 39 | $WebClient.DownloadFile($url,$output) 40 | Uzip-File -ziparchive $output -extractpath $outputDir 41 | Write-output "Extracted SQLCover to $outputDir" 42 | 43 | } 44 | Setup-SqlCodeGuard 45 | Setup-SQLCover 46 | Setup-SonarScanner -------------------------------------------------------------------------------- /examples/5-tsql/00-installTools.ps1: -------------------------------------------------------------------------------- 1 | Add-Type -AssemblyName System.IO.Compression.FileSystem 2 | 3 | function Uzip-File { 4 | param( [string]$ziparchive, [string]$extractpath ) 5 | [System.IO.Compression.ZipFile]::ExtractToDirectory( $ziparchive, $extractpath ) 6 | } 7 | 8 | $baseToolsDir = "$PSScriptRoot\tools"; 9 | New-Item -ItemType Directory -Force -Path $baseToolsDir 10 | 11 | function Setup-SqlCodeGuard { 12 | $url = "http://download.red-gate.com/SQLCodeGuardCmdLine.zip" 13 | $output = "$baseToolsDir\SQLCodeGuardCmdLine.zip"; 14 | $outputDir = "$baseToolsDir\SQLCodeGuardCmdLine" 15 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 16 | $WebClient = New-Object System.Net.WebClient 17 | $WebClient.DownloadFile($url,$output) 18 | Uzip-File -ziparchive $output -extractpath $outputDir 19 | Write-output "Extracted SQLCodeGuardCmdLine to $outputDir" 20 | } 21 | 22 | function Setup-SonarScanner { 23 | $url = "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472-windows.zip" 24 | $output = "$baseToolsDir\scanner.zip"; 25 | $outputDir = "$baseToolsDir\sonar-scanner" 26 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 27 | $WebClient = New-Object System.Net.WebClient 28 | $WebClient.DownloadFile($url,$output) 29 | Uzip-File -ziparchive $output -extractpath $outputDir 30 | Write-output "Extracted sonar-scanner to $outputDir" 31 | } 32 | 33 | function Setup-SQLCover { 34 | $url = "https://github.com/GoEddie/SQLCover/releases/download/0.4.1/SQLCOver.0.4.1.zip"; 35 | $output = "$baseToolsDir\sqlCover.zip"; 36 | $outputDir = "$baseToolsDir\SQLCover" 37 | [Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 38 | $WebClient = New-Object System.Net.WebClient 39 | $WebClient.DownloadFile($url,$output) 40 | Uzip-File -ziparchive $output -extractpath $outputDir 41 | Write-output "Extracted SQLCover to $outputDir" 42 | 43 | } 44 | Setup-SqlCodeGuard 45 | Setup-SQLCover 46 | Setup-SonarScanner -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/SnowflakeDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.snowflake.SnowflakeLexer; 4 | import org.antlr.sql.dialects.snowflake.SnowflakeParser; 5 | import org.antlr.sql.dialects.snowflake.SnowflakeParser.Group_by_clauseContext; 6 | import org.antlr.sql.dialects.snowflake.SnowflakeParser.Order_by_clauseContext; 7 | import org.antlr.sql.dialects.snowflake.SnowflakeParser.PredicateContext; 8 | import org.antlr.sql.dialects.snowflake.SnowflakeParser.Select_statementContext; 9 | import org.antlr.sql.dialects.snowflake.SnowflakeParser.Update_statementContext; 10 | import org.antlr.v4.runtime.CharStream; 11 | import org.antlr.v4.runtime.CommonTokenStream; 12 | import org.antlr.v4.runtime.Lexer; 13 | import org.antlr.v4.runtime.tree.ParseTree; 14 | 15 | public class SnowflakeDialect extends BaseDialect { 16 | 17 | @Override 18 | protected Lexer getLexer(CharStream charStream) { 19 | return new SnowflakeLexer(charStream); 20 | } 21 | 22 | @Override 23 | protected ParseTree getRoot(CommonTokenStream stream) { 24 | SnowflakeParser p = new SnowflakeParser(stream); 25 | p.removeErrorListeners(); 26 | return p.snowflake_file(); 27 | } 28 | 29 | @Override 30 | protected DialectLanguageTypesMap getTypesMap() { 31 | return new DialectLanguageTypesMap() 32 | .addCommentToken(SnowflakeLexer.SQL_COMMENT) 33 | .addCommentToken(SnowflakeLexer.LINE_COMMENT) 34 | .addCommentToken(SnowflakeLexer.LINE_COMMENT_2) 35 | .addStringToken(SnowflakeLexer.STRING) 36 | .addComplexityType(PredicateContext.class) 37 | .addCognitiveComplexityType(Order_by_clauseContext.class) 38 | .addCognitiveComplexityType(PredicateContext.class) 39 | .addCognitiveComplexityType(Select_statementContext.class) 40 | .addCognitiveComplexityType(Update_statementContext.class) 41 | .addCognitiveComplexityType(Group_by_clauseContext.class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/tools/PluginRulesPrinter.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.tools; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import org.antlr.sql.dialects.Dialects; 10 | import org.antlr.sql.dialects.SQLDialectRules; 11 | import org.sonar.plugins.sql.models.rules.Rule; 12 | import org.sonar.plugins.sql.models.rules.SqlRules; 13 | 14 | public class PluginRulesPrinter { 15 | public static void main(String[] args) { 16 | List rules = SQLDialectRules.INSTANCE.getGroupedRules(); 17 | List rules0 = SQLDialectRules.INSTANCE.getRules(); 18 | Map repos = new HashMap<>(); 19 | rules0.forEach( 20 | x -> { 21 | repos.put(x.getDialect(), x); 22 | }); 23 | for (SqlRules rule : rules) { 24 | for (Rule r : rule.getRule()) { 25 | System.out.println("## " + r.getKey() + " - " + r.getName()); 26 | 27 | List dialects = new ArrayList<>(); 28 | for (Entry re : repos.entrySet()) { 29 | if (re.getValue().getRule().stream() 30 | .anyMatch(x -> x.getKey().equals(r.getKey()))) { 31 | dialects.add(re.getKey()); 32 | } 33 | } 34 | // Header comment rules 35 | if (r.getKey().equals("C030")) { 36 | dialects = 37 | Arrays.asList(Dialects.values()).stream().map(x -> x.name()).toList(); 38 | } 39 | System.out.println( 40 | "

Supported dialects: " 41 | + String.join(",", dialects.toArray(new String[0])) 42 | + "

"); 43 | System.out.println(r.getDescription()); 44 | System.out.println(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/fillers/CpdTokensFillerTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | import org.antlr.sql.dialects.Dialects; 6 | import org.antlr.sql.models.AntlrContext; 7 | import org.apache.commons.io.FileUtils; 8 | import org.junit.Assert; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.TemporaryFolder; 12 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 13 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 14 | import org.sonar.api.batch.sensor.cpd.internal.TokensLine; 15 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 16 | import org.sonar.plugins.sql.Constants; 17 | 18 | public class CpdTokensFillerTest { 19 | 20 | @Rule public TemporaryFolder folder = new TemporaryFolder(); 21 | 22 | @Test 23 | public void test() throws Exception { 24 | SensorContextTester ctxTester = SensorContextTester.create(folder.getRoot()); 25 | ctxTester.fileSystem().setWorkDir(folder.getRoot().toPath()); 26 | 27 | File baseFile = folder.newFile("test.sql"); 28 | 29 | FileUtils.copyURLToFile(getClass().getResource("/tsql/sample1.sql"), baseFile); 30 | String contents = FileUtils.readFileToString(baseFile); 31 | 32 | DefaultInputFile ti = 33 | new TestInputFileBuilder("test", folder.getRoot(), baseFile) 34 | .initMetadata(contents) 35 | .setLanguage(Constants.languageKey) 36 | .setContents(contents) 37 | .setProjectBaseDir(folder.getRoot().toPath()) 38 | .build(); 39 | ctxTester.fileSystem().add(ti); 40 | 41 | AntlrContext antlrContext = Dialects.TSQL.parse(FileUtils.readFileToString(baseFile)); 42 | CpdTokensFiller filler = new CpdTokensFiller(); 43 | filler.fill(ti, ctxTester, antlrContext); 44 | List lines = ctxTester.cpdTokens("test:test.sql"); 45 | Assert.assertEquals(2, lines.size()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/PsSqlV2Dialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.psqlv2.PostgreSQLLexer; 4 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser; 5 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.A_exprContext; 6 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.A_expr_andContext; 7 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.Group_by_itemContext; 8 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.SelectstmtContext; 9 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.SortbyContext; 10 | import org.antlr.sql.dialects.psqlv2.PostgreSQLParser.UpdatestmtContext; 11 | import org.antlr.v4.runtime.CharStream; 12 | import org.antlr.v4.runtime.CommonTokenStream; 13 | import org.antlr.v4.runtime.Lexer; 14 | import org.antlr.v4.runtime.tree.ParseTree; 15 | 16 | public class PsSqlV2Dialect extends BaseDialect { 17 | 18 | @Override 19 | protected Lexer getLexer(CharStream charStream) { 20 | return new PostgreSQLLexer(charStream); 21 | } 22 | 23 | @Override 24 | protected ParseTree getRoot(CommonTokenStream stream) { 25 | PostgreSQLParser p = new PostgreSQLParser(stream); 26 | p.removeErrorListeners(); 27 | return p.root(); 28 | } 29 | 30 | @Override 31 | protected DialectLanguageTypesMap getTypesMap() { 32 | return new DialectLanguageTypesMap() 33 | .addCommentToken(PostgreSQLLexer.BlockComment) 34 | .addCommentToken(PostgreSQLLexer.LineComment) 35 | .addCommentToken(PostgreSQLLexer.UnterminatedBlockComment) 36 | .addStringToken(PostgreSQLLexer.StringConstant) 37 | .addComplexityType(A_exprContext.class) 38 | .addCognitiveComplexityType(SortbyContext.class) 39 | .addCognitiveComplexityType(A_expr_andContext.class) 40 | .addCognitiveComplexityType(SelectstmtContext.class) 41 | .addCognitiveComplexityType(UpdatestmtContext.class) 42 | .addCognitiveComplexityType(Group_by_itemContext.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/tools/PrettyPrinter.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.tools; 2 | 3 | import org.antlr.sql.models.AntlrContext; 4 | import org.antlr.v4.runtime.CommonTokenStream; 5 | import org.antlr.v4.runtime.Token; 6 | import org.antlr.v4.runtime.misc.Interval; 7 | import org.antlr.v4.runtime.tree.ParseTree; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | public class PrettyPrinter { 11 | 12 | public static void printTokens(AntlrContext context) { 13 | context.getAllTokens().forEach(t -> System.out.println(t)); 14 | } 15 | 16 | public static void print(final ParseTree node, final int level, CommonTokenStream stream) { 17 | final Interval sourceInterval = node.getSourceInterval(); 18 | int line = -1; 19 | int charStart = -1; 20 | int endLine = -1; 21 | int endChar = -1; 22 | String text = null; 23 | try { 24 | final Token firstToken = stream.get(sourceInterval.a); 25 | line = firstToken.getLine(); 26 | charStart = firstToken.getCharPositionInLine(); 27 | endLine = line; 28 | endChar = charStart + firstToken.getText().length(); 29 | text = firstToken.getText(); 30 | } catch (Exception e) { 31 | 32 | } 33 | 34 | String data = 35 | "@(" 36 | + line 37 | + ":" 38 | + charStart 39 | + "," 40 | + endLine 41 | + ":" 42 | + endChar 43 | + ") with text: " 44 | + text; 45 | final int tmp = level + 1; 46 | final StringBuilder sb = new StringBuilder(); 47 | sb.append(StringUtils.repeat(" ", level)); 48 | sb.append(node.getClass().getSimpleName() + ": " + data + " :" + node.getText()); 49 | System.out.println(sb.toString()); 50 | final int n = node.getChildCount(); 51 | for (int i = 0; i < n; i++) { 52 | final ParseTree c = node.getChild(i); 53 | print(c, tmp, stream); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/models/AntlrContext.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.models; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import org.antlr.sql.dialects.DialectLanguageTypesMap; 7 | import org.antlr.v4.runtime.CommonTokenStream; 8 | import org.antlr.v4.runtime.Lexer; 9 | import org.antlr.v4.runtime.Token; 10 | import org.antlr.v4.runtime.tree.ParseTree; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.sonar.plugins.sql.models.rules.Rule; 13 | import org.sonar.plugins.sql.models.rules.SqlRules; 14 | 15 | public class AntlrContext { 16 | 17 | public Lexer lexer; 18 | 19 | public ParseTree root; 20 | 21 | public CommonTokenStream stream; 22 | public boolean isCodeRules = true; 23 | public DialectLanguageTypesMap types; 24 | 25 | public String initialContents; 26 | 27 | public final List rules = new ArrayList<>(); 28 | 29 | public List getRules() { 30 | return rules; 31 | } 32 | 33 | public boolean isComment(Token token) { 34 | return types.getCommentTokens().contains(token.getType()); 35 | } 36 | 37 | public ParseTree getRoot() { 38 | return root; 39 | } 40 | 41 | public boolean isString(Token token) { 42 | return types.getStringTokens().contains(token.getType()); 43 | } 44 | 45 | public boolean isKeyword(Token token) { 46 | String symbolicName = lexer.getVocabulary().getSymbolicName(token.getType()); 47 | return symbolicName != null 48 | && ((StringUtils.equalsAnyIgnoreCase(symbolicName, token.getText())) 49 | || symbolicName.toLowerCase().contains(token.getText().toLowerCase())); 50 | } 51 | 52 | public List getAllTokens() { 53 | return stream.getTokens(); 54 | } 55 | 56 | public AntlrContext withRules(Rule... rules) { 57 | var list = Arrays.asList(rules).stream().map(x -> x.getKey()).toList(); 58 | this.rules.forEach( 59 | r -> { 60 | r.getRule().removeIf(ri -> !list.contains(ri.getKey())); 61 | }); 62 | return this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/DefaultNodesMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 6 | 7 | public class DefaultNodesMatcher implements INodesMatcher { 8 | 9 | private final IMatcher[] matchers = 10 | new IMatcher[] { 11 | new ClassNameMatcher(), new TextMatcher(), new IndexMatcher(), new DistanceMatcher() 12 | }; 13 | 14 | @SuppressWarnings("incomplete-switch") 15 | @Override 16 | public boolean matches( 17 | final IParsedNode item, final IParsedNode parent, final RuleImplementation rule) { 18 | 19 | for (final IMatcher matcher : matchers) { 20 | boolean res = matcher.match(item, rule); 21 | if (!res) { 22 | return false; 23 | } 24 | } 25 | 26 | switch (rule.getRuleMatchType()) { 27 | case FULL: 28 | if (parent == null || item == null) { 29 | return false; 30 | } 31 | if (!StringUtils.containsIgnoreCase(item.getText(), parent.getText()) 32 | && !StringUtils.containsIgnoreCase(parent.getText(), item.getText())) { 33 | return false; 34 | } 35 | break; 36 | case STRICT: 37 | if (parent == null || item == null) { 38 | return false; 39 | } 40 | if (!StringUtils.equalsIgnoreCase(item.getText(), parent.getText())) { 41 | return false; 42 | } 43 | 44 | final IParsedNode parent1 = parent.getControlFlowParent(); 45 | final IParsedNode parent2 = item.getControlFlowParent(); 46 | if (parent1 == null || parent2 == null) { 47 | return false; 48 | } 49 | if (!parent.equals(parent2)) { 50 | return false; 51 | } 52 | break; 53 | } 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/TextCheckType.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import javax.xml.bind.annotation.XmlEnum; 12 | import javax.xml.bind.annotation.XmlEnumValue; 13 | import javax.xml.bind.annotation.XmlType; 14 | import org.codehaus.plexus.util.StringUtils; 15 | 16 | /** 17 | * Java class for textCheckType. 18 | * 19 | *

The following schema fragment specifies the expected content contained within this class. 20 | * 21 | *

22 | * 23 | *

24 |  * <simpleType name="textCheckType">
25 |  *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
26 |  *     <enumeration value="Default"/>
27 |  *     <enumeration value="Contains"/>
28 |  *     <enumeration value="Regexp"/>
29 |  *     <enumeration value="Strict"/>
30 |  *   </restriction>
31 |  * </simpleType>
32 |  * 
33 | */ 34 | @XmlType(name = "textCheckType") 35 | @XmlEnum 36 | public enum TextCheckType { 37 | @XmlEnumValue("Default") 38 | DEFAULT("Default"), 39 | @XmlEnumValue("Contains") 40 | CONTAINS("Contains"), 41 | @XmlEnumValue("Regexp") 42 | REGEXP("Regexp"), 43 | @XmlEnumValue("Strict") 44 | STRICT("Strict"); 45 | private final String value; 46 | 47 | TextCheckType(String v) { 48 | value = v; 49 | } 50 | 51 | public String value() { 52 | return value; 53 | } 54 | 55 | public static TextCheckType fromValue(String v) { 56 | if (StringUtils.isBlank(v)) { 57 | return TextCheckType.DEFAULT; 58 | } 59 | 60 | for (TextCheckType c : TextCheckType.values()) { 61 | if (c.value.equalsIgnoreCase(v) || c.name().equalsIgnoreCase(v)) { 62 | return c; 63 | } 64 | } 65 | throw new IllegalArgumentException(v); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/sca/matchers/IndexMatcher.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.sca.matchers; 2 | 3 | import org.antlr.sql.sca.nodes.IParsedNode; 4 | import org.sonar.plugins.sql.models.rules.RuleDistanceIndexMatchType; 5 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 6 | 7 | public class IndexMatcher implements IMatcher { 8 | 9 | @Override 10 | public boolean match(IParsedNode node, RuleImplementation rule) { 11 | if (rule.getIndex() == 0) { 12 | return true; 13 | } 14 | if (rule.getIndex() > 0) { 15 | int val = node.getIndex(); 16 | if (val == 0) { 17 | val = node.getGlobalIndex(); 18 | } 19 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.LESS) { 20 | if (val > rule.getIndex()) { 21 | return false; 22 | } 23 | } 24 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.EQUALS) { 25 | if (val != rule.getIndex()) { 26 | return false; 27 | } 28 | } 29 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.MORE) { 30 | if (val < rule.getIndex()) { 31 | return false; 32 | } 33 | } 34 | } 35 | if (rule.getIndex() < 0) { 36 | int val = node.getIndex2(); 37 | if (val == 0) { 38 | val = node.getGlobalIndex(); 39 | } 40 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.LESS) { 41 | if (val > rule.getIndex()) { 42 | return false; 43 | } 44 | } 45 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.EQUALS) { 46 | if (val != rule.getIndex()) { 47 | return false; 48 | } 49 | } 50 | if (rule.getIndexCheckType() == RuleDistanceIndexMatchType.MORE) { 51 | if (val < rule.getIndex()) { 52 | return false; 53 | } 54 | } 55 | } 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/Constants.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | public final class Constants { 4 | public static final String languageKey = "sql"; 5 | 6 | public static final String PLUGIN_SQL_DIALECT = "sonar.sql.dialect"; 7 | 8 | public static final String PLUGIN_SQL_SCA_TIMEOUT = "sonar.sql.sca.timeout"; 9 | 10 | public static final long PLUGIN_SQL_SCA_TIMEOUT_DEFAULT = 3600l; 11 | 12 | public static final String PLUGIN_SUFFIXES = "sonar.sql.file.suffixes"; 13 | 14 | public static final String PLUGIN_SQL_EXTERNAL_RULES_SUFFIX = "sonar.sql.rules.suffixes"; 15 | 16 | public static final String PLUGIN_SQL_RULES_SKIP = "sonar.sql.rules.skip"; 17 | 18 | public static final String PLUGIN_SQL_EXTERNAL_RULES_SEARCH_PATH = "sonar.sql.rules.path"; 19 | 20 | public static final String PLUGIN_SQL_EXTERNAL_RULES_SEARCH_PATH_DEFAULT = "."; 21 | 22 | public static final String PLUGIN_SQL_EXTERNAL_RULES_SUFFIXES_DEFAULT = ".customRules"; 23 | 24 | public static final String TSQL_MS_ISSUES = "sonar.sql.tsql.ms.report"; 25 | 26 | public static final String TSQL_MS_ISSUES_DEFAULT = "staticcodeanalysis.results.xml"; 27 | 28 | public static final String TSQL_MS_REPO_KEY = "tsql-ms"; 29 | 30 | public static final String TSQL_MS_ENGINEID = "tsql-ms"; 31 | 32 | public static final String TSQL_CG_ENGINEID = "tsql-cg"; 33 | 34 | public static final String TSQL_CG_PATH_DEFAULT = 35 | "C:\\Program Files\\SQLCodeGuardCmdLine\\SqlCodeGuard40.Cmd.exe"; 36 | 37 | public static final String TSQL_CG_PATH = "sonar.sql.tsql.cg.path"; 38 | 39 | public static final String TSQL_SQLCOVER_PATH = "sonar.sql.tsql.sqlcover.report"; 40 | 41 | public static final String TSQL_SQLCOVER_PATH_DEFAULT = "Coverage.opencoverxml"; 42 | 43 | public static final String SQL_SQLCHECK_ENGINEID = "sqlcheck"; 44 | 45 | public static final String SQL_SQLCHECK_PATH = "sonar.sql.sqlcheck.path"; 46 | 47 | public static final String SQL_SQLCHECK_PATH_DEFAULT = "/usr/bin/sqlcheck"; 48 | 49 | public static final String PLUGIN_SQL_SCA_MAX_FILE_SIZE = "sonar.sql.sca.maxfilesize"; 50 | 51 | public static final long PLUGIN_SQL_SCA_MAX_FILE_SIZE_DEFAULT = 1024 * 1024 * 2l; 52 | } 53 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/sensors/CGIssuesSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.sensors; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import org.apache.commons.io.FileUtils; 7 | import org.junit.Assert; 8 | import org.junit.Assume; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.TemporaryFolder; 12 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 13 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 14 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 15 | import org.sonar.api.impl.utils.JUnitTempFolder; 16 | import org.sonar.plugins.sql.Constants; 17 | 18 | public class CGIssuesSensorTest { 19 | 20 | @Rule public TemporaryFolder folder = new TemporaryFolder(); 21 | 22 | @org.junit.Rule public JUnitTempFolder temp = new org.sonar.api.impl.utils.JUnitTempFolder(); 23 | 24 | @Test 25 | public void testExecute() throws IOException { 26 | 27 | Assume.assumeTrue("Tool exists", new File(Constants.TSQL_CG_PATH_DEFAULT).exists()); 28 | 29 | SensorContextTester ctxTester = SensorContextTester.create(folder.getRoot()); 30 | ctxTester.fileSystem().setWorkDir(folder.getRoot().toPath()); 31 | 32 | File baseFile = folder.newFile("sample.sql"); 33 | 34 | FileUtils.copyURLToFile(getClass().getResource("/tsql/sample1.sql"), baseFile); 35 | String contents = new String(Files.readAllBytes(baseFile.toPath())); 36 | 37 | DefaultInputFile ti = 38 | new TestInputFileBuilder("test", folder.getRoot(), baseFile) 39 | .initMetadata(contents) 40 | .setLanguage(Constants.languageKey) 41 | .setContents(contents) 42 | .setProjectBaseDir(folder.getRoot().toPath()) 43 | .build(); 44 | ctxTester.fileSystem().add(ti); 45 | 46 | CGIssuesSensor s = new CGIssuesSensor(temp); 47 | s.execute(ctxTester); 48 | 49 | Assert.assertEquals(1, ctxTester.allExternalIssues().size()); 50 | Assert.assertEquals(0, ctxTester.allIssues().size()); 51 | Assert.assertEquals(1, ctxTester.allAdHocRules().size()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/sensors/SQLCheckSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.sensors; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import org.apache.commons.io.FileUtils; 7 | import org.junit.Assert; 8 | import org.junit.Assume; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.TemporaryFolder; 12 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 13 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 14 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 15 | import org.sonar.api.impl.utils.JUnitTempFolder; 16 | import org.sonar.plugins.sql.Constants; 17 | 18 | public class SQLCheckSensorTest { 19 | 20 | @Rule public TemporaryFolder folder = new TemporaryFolder(); 21 | 22 | @org.junit.Rule public JUnitTempFolder temp = new org.sonar.api.impl.utils.JUnitTempFolder(); 23 | 24 | @Test 25 | public void testExecute() throws IOException { 26 | 27 | Assume.assumeTrue("Tool exists", new File(Constants.SQL_SQLCHECK_PATH_DEFAULT).exists()); 28 | SensorContextTester ctxTester = SensorContextTester.create(folder.getRoot()); 29 | ctxTester.fileSystem().setWorkDir(folder.getRoot().toPath()); 30 | 31 | File baseFile = folder.newFile("sample2.sql"); 32 | 33 | FileUtils.copyURLToFile(getClass().getResource("/tsql/sample1.sql"), baseFile); 34 | String contents = new String(Files.readAllBytes(baseFile.toPath())); 35 | 36 | DefaultInputFile ti = 37 | new TestInputFileBuilder("test", folder.getRoot(), baseFile) 38 | .initMetadata(contents) 39 | .setLanguage(Constants.languageKey) 40 | .setContents(contents) 41 | .setProjectBaseDir(folder.getRoot().toPath()) 42 | .build(); 43 | ctxTester.fileSystem().add(ti); 44 | 45 | SqlCheckSensor s = new SqlCheckSensor(temp); 46 | s.execute(ctxTester); 47 | 48 | Assert.assertEquals(3, ctxTester.allExternalIssues().size()); 49 | Assert.assertEquals(0, ctxTester.allIssues().size()); 50 | Assert.assertEquals(3, ctxTester.allAdHocRules().size()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/external/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.sonar.test 6 | sample 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | sql-grammars 11 | 12 | 13 | UTF-8 14 | 1.8 15 | . 16 | tsql 17 | org.antlr.sql.dialects.tsql 18 | 19 | 20 | 21 | 22 | 23 | org.antlr 24 | antlr4-maven-plugin 25 | 4.11.1 26 | 27 | ${basedir} 28 | ${basedir}/../sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/${grammars.out.dir} 29 | true 30 | true 31 | UPPER 32 | 33 | -package 34 | ${grammars.package} 35 | -Xexact-output-dir 36 | 37 | 38 | 39 | ${grammars}/*.g4 40 | 41 | 42 | 43 | 44 | 45 | antlr4 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.10.1 58 | 59 | ${jdk.min.version} 60 | ${jdk.min.version} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/SQLRulesDefinition.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import org.antlr.sql.dialects.SQLDialectRules; 6 | import org.sonar.api.rules.RuleType; 7 | import org.sonar.api.server.debt.DebtRemediationFunction; 8 | import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; 9 | import org.sonar.api.server.rule.RulesDefinition; 10 | import org.sonar.plugins.sql.models.rules.SqlRules; 11 | 12 | public class SQLRulesDefinition implements RulesDefinition { 13 | 14 | @Override 15 | public void define(Context context) { 16 | 17 | List rules = SQLDialectRules.INSTANCE.getGroupedRules(); 18 | 19 | for (SqlRules rulesDef : rules) { 20 | NewRepository repository = 21 | context.createRepository(rulesDef.getRepoKey(), Constants.languageKey) 22 | .setName(rulesDef.getRepoName()); 23 | 24 | for (org.sonar.plugins.sql.models.rules.Rule rule : rulesDef.getRule()) { 25 | NewRule x1Rule = 26 | repository 27 | .createRule(rule.getKey()) 28 | .setName(rule.getName()) 29 | .setHtmlDescription(rule.getDescription()) 30 | .addTags(rule.getTag()) 31 | .setSeverity(rule.getSeverity()); 32 | String gapMultiplier = rule.getDebtRemediationFunctionCoefficient(); 33 | String baseEffort = rule.getRemediationFunctionBaseEffort(); 34 | DebtRemediationFunction func = 35 | new DefaultDebtRemediationFunction( 36 | DebtRemediationFunction.Type.valueOf(rule.getRemediationFunction()), 37 | (Objects.equals(gapMultiplier, "")) ? null : gapMultiplier, 38 | (Objects.equals(baseEffort, "")) ? null : baseEffort); 39 | x1Rule.setDebtRemediationFunction(func); 40 | x1Rule.setType(RuleType.valueOf(rule.getRuleType())); 41 | x1Rule.setActivatedByDefault(true); 42 | } 43 | repository.done(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammar.interp: -------------------------------------------------------------------------------- 1 | token literal names: 2 | null 3 | null 4 | null 5 | null 6 | null 7 | '\'' 8 | null 9 | null 10 | null 11 | null 12 | null 13 | null 14 | null 15 | null 16 | '=' 17 | '>' 18 | '<' 19 | '!' 20 | '+=' 21 | '-=' 22 | '*=' 23 | '/=' 24 | '%=' 25 | '&=' 26 | '^=' 27 | '|=' 28 | '||' 29 | '.' 30 | '_' 31 | '@' 32 | '#' 33 | '$' 34 | '(' 35 | ')' 36 | ',' 37 | ';' 38 | ':' 39 | '::' 40 | '*' 41 | '/' 42 | '%' 43 | '+' 44 | '-' 45 | '~' 46 | '|' 47 | '&' 48 | '^' 49 | 50 | token symbolic names: 51 | null 52 | WS 53 | COMMENT 54 | LINE_COMMENT 55 | DOUBLE_QUOTE_ID 56 | SINGLE_QUOTE 57 | SQUARE_BRACKET_ID 58 | LOCAL_ID 59 | DECIMAL 60 | ID 61 | STRING 62 | BINARY 63 | FLOAT 64 | REAL 65 | EQUAL 66 | GREATER 67 | LESS 68 | EXCLAMATION 69 | PLUS_ASSIGN 70 | MINUS_ASSIGN 71 | MULT_ASSIGN 72 | DIV_ASSIGN 73 | MOD_ASSIGN 74 | AND_ASSIGN 75 | XOR_ASSIGN 76 | OR_ASSIGN 77 | DOUBLE_BAR 78 | DOT 79 | UNDERLINE 80 | AT 81 | SHARP 82 | DOLLAR 83 | LR_BRACKET 84 | RR_BRACKET 85 | COMMA 86 | SEMI 87 | COLON 88 | DOUBLE_COLON 89 | STAR 90 | DIVIDE 91 | MODULE 92 | PLUS 93 | MINUS 94 | BIT_NOT 95 | BIT_OR 96 | BIT_AND 97 | BIT_XOR 98 | 99 | rule names: 100 | root 101 | statement 102 | comment 103 | multiline_comment 104 | line_comment 105 | non_comment 106 | 107 | 108 | atn: 109 | [4, 1, 46, 35, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 4, 0, 14, 8, 0, 11, 0, 12, 0, 15, 1, 0, 3, 0, 19, 8, 0, 1, 1, 1, 1, 3, 1, 23, 8, 1, 1, 2, 1, 2, 3, 2, 27, 8, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 1, 2, 0, 4, 4, 6, 46, 32, 0, 13, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 26, 1, 0, 0, 0, 6, 28, 1, 0, 0, 0, 8, 30, 1, 0, 0, 0, 10, 32, 1, 0, 0, 0, 12, 14, 3, 2, 1, 0, 13, 12, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 13, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 19, 5, 0, 0, 1, 18, 17, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 1, 1, 0, 0, 0, 20, 23, 3, 4, 2, 0, 21, 23, 3, 10, 5, 0, 22, 20, 1, 0, 0, 0, 22, 21, 1, 0, 0, 0, 23, 3, 1, 0, 0, 0, 24, 27, 3, 6, 3, 0, 25, 27, 3, 8, 4, 0, 26, 24, 1, 0, 0, 0, 26, 25, 1, 0, 0, 0, 27, 5, 1, 0, 0, 0, 28, 29, 5, 2, 0, 0, 29, 7, 1, 0, 0, 0, 30, 31, 5, 3, 0, 0, 31, 9, 1, 0, 0, 0, 32, 33, 7, 0, 0, 0, 33, 11, 1, 0, 0, 0, 4, 15, 18, 22, 26] -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/Names.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}textItem" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"textItem"}) 39 | @XmlRootElement(name = "names") 40 | public class Names { 41 | 42 | protected List textItem; 43 | 44 | /** 45 | * Gets the value of the textItem property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the textItem property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getTextItem().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link String } 58 | */ 59 | public List getTextItem() { 60 | if (textItem == null) { 61 | textItem = new ArrayList(); 62 | } 63 | return this.textItem; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/TextToFind.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}textItem" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"textItem"}) 39 | @XmlRootElement(name = "textToFind") 40 | public class TextToFind { 41 | 42 | protected List textItem; 43 | 44 | /** 45 | * Gets the value of the textItem property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the textItem property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getTextItem().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link String } 58 | */ 59 | public List getTextItem() { 60 | if (textItem == null) { 61 | textItem = new ArrayList(); 62 | } 63 | return this.textItem; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/PsSqlDialect.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import org.antlr.sql.dialects.psqlv1.PostgreSQLLexer; 4 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser; 5 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.Combine_clauseContext; 6 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.Grouping_elemContext; 7 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.Order_by_itemContext; 8 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.PredicateContext; 9 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.Select_stmtContext; 10 | import org.antlr.sql.dialects.psqlv1.PostgreSQLParser.Update_stmtContext; 11 | import org.antlr.v4.runtime.CharStream; 12 | import org.antlr.v4.runtime.CommonTokenStream; 13 | import org.antlr.v4.runtime.Lexer; 14 | import org.antlr.v4.runtime.tree.ParseTree; 15 | 16 | public class PsSqlDialect extends BaseDialect { 17 | 18 | @Override 19 | protected Lexer getLexer(CharStream charStream) { 20 | return new PostgreSQLLexer(charStream); 21 | } 22 | 23 | @Override 24 | protected ParseTree getRoot(CommonTokenStream stream) { 25 | PostgreSQLParser p = new PostgreSQLParser(stream); 26 | p.removeErrorListeners(); 27 | return p.root(); 28 | } 29 | 30 | @Override 31 | protected DialectLanguageTypesMap getTypesMap() { 32 | return new DialectLanguageTypesMap() 33 | .addCommentToken(PostgreSQLParser.COMMENT) 34 | .addCommentToken(PostgreSQLParser.BLOCK_COMMENT) 35 | .addCommentToken(PostgreSQLParser.LINE_COMMENT) 36 | .addStringToken(PostgreSQLParser.SINGLEQ_STRING_LITERAL) 37 | .addStringToken(PostgreSQLParser.DOUBLEQ_STRING_LITERAL) 38 | .addComplexityType(PredicateContext.class) 39 | .addCognitiveComplexityType(Order_by_itemContext.class) 40 | .addCognitiveComplexityType(PredicateContext.class) 41 | // 42 | .addCognitiveComplexityType(Update_stmtContext.class) 43 | .addCognitiveComplexityType(Combine_clauseContext.class) 44 | .addCognitiveComplexityType(Grouping_elemContext.class) 45 | .addCognitiveComplexityType(Select_stmtContext.class) 46 | // .addComplexityType(Grouping_elemContext.class) 47 | ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/RuleDistanceIndexMatchType.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import javax.xml.bind.annotation.XmlEnum; 12 | import javax.xml.bind.annotation.XmlEnumValue; 13 | import javax.xml.bind.annotation.XmlType; 14 | import org.codehaus.plexus.util.StringUtils; 15 | 16 | /** 17 | * Java class for ruleDistanceIndexMatchType. 18 | * 19 | *

The following schema fragment specifies the expected content contained within this class. 20 | * 21 | *

22 | * 23 | *

24 |  * <simpleType name="ruleDistanceIndexMatchType">
25 |  *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
26 |  *     <enumeration value="Default"/>
27 |  *     <enumeration value="More"/>
28 |  *     <enumeration value="Less"/>
29 |  *     <enumeration value="Equals"/>
30 |  *   </restriction>
31 |  * </simpleType>
32 |  * 
33 | */ 34 | @XmlType(name = "ruleDistanceIndexMatchType") 35 | @XmlEnum 36 | public enum RuleDistanceIndexMatchType { 37 | @XmlEnumValue("Default") 38 | DEFAULT("Default"), 39 | @XmlEnumValue("More") 40 | MORE("More"), 41 | @XmlEnumValue("Less") 42 | LESS("Less"), 43 | @XmlEnumValue("BeforeOrAfter") 44 | BEFOREORAFTER("BeforeOrAfter"), 45 | @XmlEnumValue("Equals") 46 | EQUALS("Equals"); 47 | private final String value; 48 | 49 | RuleDistanceIndexMatchType(String v) { 50 | value = v; 51 | } 52 | 53 | public String value() { 54 | return value; 55 | } 56 | 57 | public static RuleDistanceIndexMatchType fromValue(String v) { 58 | if (StringUtils.isBlank(v)) { 59 | return RuleDistanceIndexMatchType.DEFAULT; 60 | } 61 | 62 | for (RuleDistanceIndexMatchType c : RuleDistanceIndexMatchType.values()) { 63 | if (c.value.equalsIgnoreCase(v) || c.name().equalsIgnoreCase(v)) { 64 | return c; 65 | } 66 | } 67 | throw new IllegalArgumentException(v); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/external/rule1.customRules: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C002 6 | test 7 | IC002 8 | <h2>Description</h2><p>SELECT * is used. Please list names.</p><h2>Code examples</h2><h3>Non-compliant</h3><pre><code>SELECT * from dbo.test;</code></pre><h3>Compliant</h3><pre><code>SELECT name, surname from dbo.test;</code></pre><pre><code>SELECT name, surname, 1 * 3 from dbo.test;</code></pre> 9 | MINOR 10 | LINEAR 11 | 2min 12 | design 13 | BUG 14 | 15 | 16 | Select_list_elemContext 17 | 18 | 19 | * 20 | 21 | 22 | 23 | 24 | 25 | DEMO: SELECT * was used 26 | 10 27 | 10 28 | 10 29 | Default 30 | Default 31 | Default 32 | TextAndClass 33 | FailIfFound 34 | Strict 35 | 36 | SELECT * from dbo.test; 37 | 38 | 39 | SELECT name, surname from dbo.test; 40 | SELECT name, surname, 1 * 3 from dbo.test; 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammarBaseVisitor.java: -------------------------------------------------------------------------------- 1 | // Generated from java-escape by ANTLR 4.11.1 2 | package org.antlr.sql.dialects.comments; 3 | import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; 4 | 5 | /** 6 | * This class provides an empty implementation of {@link CommentsGrammarVisitor}, 7 | * which can be extended to create a visitor which only needs to handle a subset 8 | * of the available methods. 9 | * 10 | * @param The return type of the visit operation. Use {@link Void} for 11 | * operations with no return type. 12 | */ 13 | @SuppressWarnings("CheckReturnValue") 14 | public class CommentsGrammarBaseVisitor extends AbstractParseTreeVisitor implements CommentsGrammarVisitor { 15 | /** 16 | * {@inheritDoc} 17 | * 18 | *

The default implementation returns the result of calling 19 | * {@link #visitChildren} on {@code ctx}.

20 | */ 21 | @Override public T visitRoot(CommentsGrammarParser.RootContext ctx) { return visitChildren(ctx); } 22 | /** 23 | * {@inheritDoc} 24 | * 25 | *

The default implementation returns the result of calling 26 | * {@link #visitChildren} on {@code ctx}.

27 | */ 28 | @Override public T visitStatement(CommentsGrammarParser.StatementContext ctx) { return visitChildren(ctx); } 29 | /** 30 | * {@inheritDoc} 31 | * 32 | *

The default implementation returns the result of calling 33 | * {@link #visitChildren} on {@code ctx}.

34 | */ 35 | @Override public T visitComment(CommentsGrammarParser.CommentContext ctx) { return visitChildren(ctx); } 36 | /** 37 | * {@inheritDoc} 38 | * 39 | *

The default implementation returns the result of calling 40 | * {@link #visitChildren} on {@code ctx}.

41 | */ 42 | @Override public T visitMultiline_comment(CommentsGrammarParser.Multiline_commentContext ctx) { return visitChildren(ctx); } 43 | /** 44 | * {@inheritDoc} 45 | * 46 | *

The default implementation returns the result of calling 47 | * {@link #visitChildren} on {@code ctx}.

48 | */ 49 | @Override public T visitLine_comment(CommentsGrammarParser.Line_commentContext ctx) { return visitChildren(ctx); } 50 | /** 51 | * {@inheritDoc} 52 | * 53 | *

The default implementation returns the result of calling 54 | * {@link #visitChildren} on {@code ctx}.

55 | */ 56 | @Override public T visitNon_comment(CommentsGrammarParser.Non_commentContext ctx) { return visitChildren(ctx); } 57 | } -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/RuleMatchType.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import javax.xml.bind.annotation.XmlEnum; 12 | import javax.xml.bind.annotation.XmlEnumValue; 13 | import javax.xml.bind.annotation.XmlType; 14 | import org.codehaus.plexus.util.StringUtils; 15 | 16 | /** 17 | * Java class for ruleMatchType. 18 | * 19 | *

The following schema fragment specifies the expected content contained within this class. 20 | * 21 | *

22 | * 23 | *

24 |  * <simpleType name="ruleMatchType">
25 |  *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
26 |  *     <enumeration value="Default"/>
27 |  *     <enumeration value="Full"/>
28 |  *     <enumeration value="TextOnly"/>
29 |  *     <enumeration value="TextAndClass"/>
30 |  *     <enumeration value="ClassOnly"/>
31 |  *     <enumeration value="Strict"/>
32 |  *   </restriction>
33 |  * </simpleType>
34 |  * 
35 | */ 36 | @XmlType(name = "ruleMatchType") 37 | @XmlEnum 38 | public enum RuleMatchType { 39 | @XmlEnumValue("Default") 40 | DEFAULT("Default"), 41 | @XmlEnumValue("Full") 42 | FULL("Full"), 43 | @XmlEnumValue("TextOnly") 44 | TEXT_ONLY("TextOnly"), 45 | @XmlEnumValue("TextAndClass") 46 | TEXT_AND_CLASS("TextAndClass"), 47 | @XmlEnumValue("ClassOnly") 48 | CLASS_ONLY("ClassOnly"), 49 | @XmlEnumValue("Strict") 50 | STRICT("Strict"); 51 | private final String value; 52 | 53 | RuleMatchType(String v) { 54 | value = v; 55 | } 56 | 57 | public String value() { 58 | return value; 59 | } 60 | 61 | public static RuleMatchType fromValue(String v) { 62 | if (StringUtils.isBlank(v)) { 63 | return RuleMatchType.DEFAULT; 64 | } 65 | for (RuleMatchType c : RuleMatchType.values()) { 66 | if (c.value.equalsIgnoreCase(v) || c.name().equalsIgnoreCase(v)) { 67 | return c; 68 | } 69 | } 70 | throw new IllegalArgumentException(v); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/tools/ClassesLister.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.tools; 2 | 3 | import com.google.common.reflect.ClassPath; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.TreeSet; 9 | import org.apache.commons.io.FileUtils; 10 | 11 | public class ClassesLister { 12 | 13 | private static class ListData { 14 | public String dialect; 15 | public String packageName; 16 | 17 | public ListData(String dialect, String packageName) { 18 | this.dialect = dialect; 19 | this.packageName = packageName; 20 | } 21 | } 22 | 23 | public static void main(String[] args) throws IOException { 24 | 25 | List data = 26 | Arrays.asList( 27 | new ListData("TSQL", "org.antlr.sql.dialects.tsql.TSqlParser$"), 28 | new ListData("PSSQL", "org.antlr.sql.dialects.psql.PostgreSQLParser$"), 29 | new ListData("MySQL", "org.antlr.sql.dialects.mysql.MySqlParser$"), 30 | new ListData("VSQL", "org.antlr.sql.dialects.vsql.VSqlParser$"), 31 | new ListData("PSSQLV2", "org.antlr.sql.dialects.psqlv2.PostgreSQLParser$"), 32 | new ListData( 33 | "SNOWFLAKE", "org.antlr.sql.dialects.snowflake.SnowflakeParser$")); 34 | 35 | final ClassLoader loader = Thread.currentThread().getContextClassLoader(); 36 | 37 | for (ListData dataItem : data) { 38 | TreeSet set = new TreeSet<>(); 39 | for (final ClassPath.ClassInfo info : ClassPath.from(loader).getAllClasses()) { 40 | if (info.getName().startsWith(dataItem.packageName)) { 41 | set.add("- " + info.getSimpleName()); 42 | } 43 | } 44 | 45 | File outFile = 46 | new File("./../../docs/" + dataItem.dialect.toLowerCase() + "Classes.md"); 47 | outFile.delete(); 48 | FileUtils.writeLines( 49 | outFile, Arrays.asList("# " + dataItem.dialect, "Supported classes:"), true); 50 | FileUtils.writeLines(outFile, set, true); 51 | System.out.println( 52 | "Written: " + outFile.getCanonicalFile().getAbsolutePath() + " " + set.size()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/CpdTokensFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.models.AntlrContext; 5 | import org.antlr.v4.runtime.Token; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.fs.TextRange; 9 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 10 | import org.sonar.api.batch.sensor.SensorContext; 11 | import org.sonar.api.batch.sensor.cpd.NewCpdTokens; 12 | import org.sonar.api.utils.log.Logger; 13 | import org.sonar.api.utils.log.Loggers; 14 | 15 | public class CpdTokensFiller implements Filler { 16 | private static final Logger LOGGER = Loggers.get(CpdTokensFiller.class); 17 | 18 | @Override 19 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 20 | try { 21 | if (!(file instanceof DefaultInputFile)) { 22 | return; 23 | } 24 | NewCpdTokens newCpdTokens = context.newCpdTokens().onFile(file); 25 | List tokens = antlrContext.getAllTokens(); 26 | DefaultInputFile defaultInputFile = (DefaultInputFile) file; 27 | for (Token token : tokens) { 28 | try { 29 | final String text = token.getText(); 30 | if (token.getType() == -1 31 | || token.getStartIndex() >= token.getStopIndex() 32 | || StringUtils.isEmpty(text)) { 33 | continue; 34 | } 35 | 36 | final TextRange range = 37 | defaultInputFile.newRange( 38 | token.getStartIndex(), token.getStopIndex() + 1); 39 | defaultInputFile.validate(range); 40 | newCpdTokens.addToken(range, text); 41 | 42 | } catch (Throwable e) { 43 | if (LOGGER.isDebugEnabled()) { 44 | LOGGER.debug( 45 | "Cannot add range: {} on file {} for token {}", e, file, token); 46 | } 47 | } 48 | } 49 | synchronized (context) { 50 | newCpdTokens.save(); 51 | } 52 | } catch (Throwable e) { 53 | LOGGER.warn("Error adding cpd tokens on " + file, e); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/CompliantRulesCodeExamples.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleCodeExample" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleCodeExample"}) 39 | @XmlRootElement(name = "compliantRulesCodeExamples") 40 | public class CompliantRulesCodeExamples { 41 | 42 | protected List ruleCodeExample; 43 | 44 | /** 45 | * Gets the value of the ruleCodeExample property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleCodeExample property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleCodeExample().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link String } 58 | */ 59 | public List getRuleCodeExample() { 60 | if (ruleCodeExample == null) { 61 | ruleCodeExample = new ArrayList(); 62 | } 63 | return this.ruleCodeExample; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/ViolatingRulesCodeExamples.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleCodeExample" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleCodeExample"}) 39 | @XmlRootElement(name = "violatingRulesCodeExamples") 40 | public class ViolatingRulesCodeExamples { 41 | 42 | protected List ruleCodeExample; 43 | 44 | /** 45 | * Gets the value of the ruleCodeExample property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleCodeExample property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleCodeExample().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link String } 58 | */ 59 | public List getRuleCodeExample() { 60 | if (ruleCodeExample == null) { 61 | ruleCodeExample = new ArrayList(); 62 | } 63 | return this.ruleCodeExample; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/UsesRules.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleImplementation" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleImplementation"}) 39 | @XmlRootElement(name = "usesRules") 40 | public class UsesRules { 41 | 42 | protected List ruleImplementation; 43 | 44 | /** 45 | * Gets the value of the ruleImplementation property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleImplementation property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleImplementation().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link RuleImplementation } 58 | */ 59 | public List getRuleImplementation() { 60 | if (ruleImplementation == null) { 61 | ruleImplementation = new ArrayList(); 62 | } 63 | return this.ruleImplementation; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/ParentRules.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleImplementation" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleImplementation"}) 39 | @XmlRootElement(name = "parentRules") 40 | public class ParentRules { 41 | 42 | protected List ruleImplementation; 43 | 44 | /** 45 | * Gets the value of the ruleImplementation property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleImplementation property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleImplementation().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link RuleImplementation } 58 | */ 59 | public List getRuleImplementation() { 60 | if (ruleImplementation == null) { 61 | ruleImplementation = new ArrayList(); 62 | } 63 | return this.ruleImplementation; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/ChildrenRules.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleImplementation" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleImplementation"}) 39 | @XmlRootElement(name = "childrenRules") 40 | public class ChildrenRules { 41 | 42 | protected List ruleImplementation; 43 | 44 | /** 45 | * Gets the value of the ruleImplementation property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleImplementation property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleImplementation().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link RuleImplementation } 58 | */ 59 | public List getRuleImplementation() { 60 | if (ruleImplementation == null) { 61 | ruleImplementation = new ArrayList(); 62 | } 63 | return this.ruleImplementation; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/SiblingsRules.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import javax.xml.bind.annotation.XmlAccessType; 14 | import javax.xml.bind.annotation.XmlAccessorType; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlType; 17 | 18 | /** 19 | * Java class for anonymous complex type. 20 | * 21 | *

The following schema fragment specifies the expected content contained within this class. 22 | * 23 | *

24 |  * <complexType>
25 |  *   <complexContent>
26 |  *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
27 |  *       <sequence>
28 |  *         <element ref="{}ruleImplementation" maxOccurs="unbounded" minOccurs="0"/>
29 |  *       </sequence>
30 |  *     </restriction>
31 |  *   </complexContent>
32 |  * </complexType>
33 |  * 
34 | */ 35 | @XmlAccessorType(XmlAccessType.FIELD) 36 | @XmlType( 37 | name = "", 38 | propOrder = {"ruleImplementation"}) 39 | @XmlRootElement(name = "siblingsRules") 40 | public class SiblingsRules { 41 | 42 | protected List ruleImplementation; 43 | 44 | /** 45 | * Gets the value of the ruleImplementation property. 46 | * 47 | *

This accessor method returns a reference to the live list, not a snapshot. Therefore any 48 | * modification you make to the returned list will be present inside the JAXB object. This is 49 | * why there is not a set method for the ruleImplementation property. 50 | * 51 | *

For example, to add a new item, do as follows: 52 | * 53 | *

54 |      *    getRuleImplementation().add(newItem);
55 |      * 
56 | * 57 | *

Objects of the following type(s) are allowed in the list {@link RuleImplementation } 58 | */ 59 | public List getRuleImplementation() { 60 | if (ruleImplementation == null) { 61 | ruleImplementation = new ArrayList(); 62 | } 63 | return this.ruleImplementation; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/antlr/sql/tools/TestFilesGenerator.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.tools; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.List; 7 | import org.antlr.sql.dialects.SQLDialectRules; 8 | import org.apache.commons.io.FileUtils; 9 | import org.sonar.plugins.sql.models.rules.Rule; 10 | import org.sonar.plugins.sql.models.rules.SqlRules; 11 | 12 | public class TestFilesGenerator { 13 | 14 | public static void main(String[] args) { 15 | List rules = SQLDialectRules.INSTANCE.getRules(); 16 | 17 | for (SqlRules rule : rules) { 18 | String dir = rule.getDialect() == null ? "other" : rule.getDialect(); 19 | if (dir == null) { 20 | dir = "other"; 21 | } 22 | dir = "./examples/" + dir.toLowerCase() + "/src"; 23 | 24 | for (Rule r : rule.getRule()) { 25 | StringBuilder sb = new StringBuilder(); 26 | sb.append("/*compliant code*/\r\n"); 27 | r.getRuleImplementation() 28 | .getCompliantRulesCodeExamples() 29 | .getRuleCodeExample() 30 | .forEach( 31 | line -> { 32 | sb.append(line); 33 | sb.append("\r\n"); 34 | sb.append(line); 35 | sb.append("\r\n"); 36 | }); 37 | sb.append("/*violating code*/\r\n"); 38 | r.getRuleImplementation() 39 | .getViolatingRulesCodeExamples() 40 | .getRuleCodeExample() 41 | .forEach( 42 | line -> { 43 | sb.append(line); 44 | sb.append("\r\n"); 45 | sb.append(line); 46 | sb.append("\r\n"); 47 | }); 48 | File outFile = new File(dir, r.getKey().toLowerCase() + ".sql"); 49 | try { 50 | FileUtils.write(outFile, sb.toString(), StandardCharsets.UTF_8); 51 | } catch (IOException e) { 52 | // TODO Auto-generated catch block 53 | e.printStackTrace(); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/external/rule3.customRules: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C002 6 | test 7 | IC002 8 | <h2>Description</h2><p>SELECT * is used. Please list names.</p><h2>Code examples</h2><h3>Non-compliant</h3><pre><code>SELECT * from dbo.test;</code></pre><h3>Compliant</h3><pre><code>SELECT name, surname from dbo.test;</code></pre><pre><code>SELECT name, surname, 1 * 3 from dbo.test;</code></pre> 9 | MINOR 10 | LINEAR 11 | 2min 12 | design 13 | BUG 14 | comments 15 | file 16 | 17 | 18 | Select_list_elemContext 19 | 20 | 21 | * 22 | 23 | 24 | 25 | 26 | 27 | DEMO: SELECT * was used 28 | 10 29 | 10 30 | 10 31 | Default 32 | Default 33 | Default 34 | TextAndClass 35 | FailIfFound 36 | Strict 37 | 38 | SELECT * from dbo.test; 39 | 40 | 41 | SELECT name, surname from dbo.test; 42 | SELECT name, surname, 1 * 3 from dbo.test; 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/1-tsql/02-runSonar.ps1: -------------------------------------------------------------------------------- 1 | $baseToolsDir = "$PSScriptRoot\tools"; 2 | New-Item -ItemType Directory -Force -Path $baseToolsDir 3 | 4 | function Start-SQLCover { 5 | $database = "ExampleDatabase" 6 | $server = ".\SQLEXPRESS01" 7 | $cloverDir = "$PSScriptRoot\tools\SQLCover" 8 | $coverageOutputDir = "$PSScriptRoot\build\sqlcoverresults" 9 | 10 | . "$cloverDir\SQLCover.ps1" 11 | $results = Get-CoverTSql "$cloverDir\SQLCover.dll" "server=$server;initial catalog=$database;integrated security=sspi;" "$database " "exec tSQLt.RunAll" 12 | New-Item -ItemType Directory -Force -Path $coverageOutputDir 13 | Export-OpenXml $results "$coverageOutputDir" 14 | } 15 | 16 | function Start-MsBuild { 17 | $dbProject = "$PSScriptRoot\src\ExampleDatabase\ExampleDatabase.sln" 18 | $msbuildpath = Resolve-Path "C:\Program Files*\MSBuild\*\Bin\*\MSBuild.exe" | select -ExpandProperty Path -First 1 19 | &$msbuildpath "$dbProject" /t:build /p:RunSqlCodeAnalysis=True 20 | } 21 | 22 | function Start-DotnetMsBuild { 23 | $dbProject = "$PSScriptRoot\src\ExampleDatabase\ExampleDatabase.sln" 24 | $msbuildpath = "dotnet" 25 | $msbuildArgs = @("msbuild", "$dbProject", "/t:build", "/p:RunSqlCodeAnalysis=True") 26 | Write-Output "$msbuildpath $msbuildArgs" 27 | &$msbuildpath $msbuildArgs 28 | } 29 | 30 | 31 | function Start-Sonar { 32 | $sqlCodeGuard = "$PSScriptRoot\tools\SQLCodeGuardCmdLine\SqlCodeGuard30.Cmd.exe" 33 | $sonarScanner = Resolve-Path "$PSScriptRoot\tools\sonar-scanner\*\bin\sonar-scanner.bat" | select -ExpandProperty Path -First 1 34 | 35 | $sonarArgs = @( 36 | "-Dsonar.host.url=http://localhost:9000" 37 | "-Dsonar.exclusions=**/bin/**/*.*,**/obj/**/*.*,**/*.sqlproj", # skip build files from analysis 38 | 39 | # it is possible to specify absolute path to the SQLCover report or directory where file matching *Coverage.opencoverxml resides, by default plugin will try to find it in the base directory's subdirectories 40 | # "-Dsonar.sql.tsql.sqlcover.report=$coverageOutputDir\Coverage.opencoverxml", 41 | 42 | # setting sql code guard path 43 | "-Dsonar.sql.tsql.cg.path=$sqlCodeGuard", 44 | "-X" 45 | 46 | # it is possible to specify absolute path to the MSBuild code analysis report or directory where file matching *StaticCodeAnalysis.Results.xml resides, by default plugin will try to find it in the base directory's subdirectories 47 | #"-Dsonar.sql.tsql.ms.report=$PSScriptRoot\src\ExampleDatabase\ExampleDatabase\bin\Debug" 48 | 49 | ); 50 | &$sonarScanner $sonarArgs 51 | } 52 | 53 | ## Uncomment where required 54 | 55 | #Start-SQLCOver 56 | #Start-MsBuild 57 | #Start-DotnetMsBuild 58 | Start-Sonar -------------------------------------------------------------------------------- /examples/5-tsql/02-runSonar.ps1: -------------------------------------------------------------------------------- 1 | $baseToolsDir = "$PSScriptRoot\tools"; 2 | New-Item -ItemType Directory -Force -Path $baseToolsDir 3 | 4 | function Start-SQLCover { 5 | $database = "ExampleDatabase" 6 | $server = ".\SQLEXPRESS01" 7 | $cloverDir = "$PSScriptRoot\tools\SQLCover" 8 | $coverageOutputDir = "$PSScriptRoot\build\sqlcoverresults" 9 | 10 | . "$cloverDir\SQLCover.ps1" 11 | $results = Get-CoverTSql "$cloverDir\SQLCover.dll" "server=$server;initial catalog=$database;integrated security=sspi;" "$database " "exec tSQLt.RunAll" 12 | New-Item -ItemType Directory -Force -Path $coverageOutputDir 13 | Export-OpenXml $results "$coverageOutputDir" 14 | } 15 | 16 | function Start-MsBuild { 17 | $dbProject = "$PSScriptRoot\src\ExampleDatabase\ExampleDatabase.sln" 18 | $msbuildpath = Resolve-Path "C:\Program Files*\MSBuild\*\Bin\*\MSBuild.exe" | select -ExpandProperty Path -First 1 19 | &$msbuildpath "$dbProject" /t:build /p:RunSqlCodeAnalysis=True 20 | } 21 | 22 | function Start-DotnetMsBuild { 23 | $dbProject = "$PSScriptRoot\src\ExampleDatabase\ExampleDatabase.sln" 24 | $msbuildpath = "dotnet" 25 | $msbuildArgs = @("msbuild", "$dbProject", "/t:build", "/p:RunSqlCodeAnalysis=True") 26 | Write-Output "$msbuildpath $msbuildArgs" 27 | &$msbuildpath $msbuildArgs 28 | } 29 | 30 | 31 | function Start-Sonar { 32 | $sqlCodeGuard = "$PSScriptRoot\tools\SQLCodeGuardCmdLine\SqlCodeGuard30.Cmd.exe" 33 | $sonarScanner = Resolve-Path "$PSScriptRoot\tools\sonar-scanner\*\bin\sonar-scanner.bat" | select -ExpandProperty Path -First 1 34 | 35 | $sonarArgs = @( 36 | "-Dsonar.host.url=http://localhost:9000" 37 | "-Dsonar.exclusions=**/bin/**/*.*,**/obj/**/*.*,**/*.sqlproj", # skip build files from analysis 38 | 39 | # it is possible to specify absolute path to the SQLCover report or directory where file matching *Coverage.opencoverxml resides, by default plugin will try to find it in the base directory's subdirectories 40 | # "-Dsonar.sql.tsql.sqlcover.report=$coverageOutputDir\Coverage.opencoverxml", 41 | 42 | # setting sql code guard path 43 | "-Dsonar.sql.tsql.cg.path=$sqlCodeGuard" 44 | #,"-X" 45 | 46 | # it is possible to specify absolute path to the MSBuild code analysis report or directory where file matching *StaticCodeAnalysis.Results.xml resides, by default plugin will try to find it in the base directory's subdirectories 47 | #"-Dsonar.sql.tsql.ms.report=$PSScriptRoot\src\ExampleDatabase\ExampleDatabase\bin\Debug" 48 | 49 | ); 50 | &$sonarScanner $sonarArgs 51 | } 52 | 53 | ## Uncomment where required 54 | 55 | #Start-SQLCOver 56 | #Start-MsBuild 57 | #Start-DotnetMsBuild 58 | Start-Sonar -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/visitors/RulesMatchingVisitor2.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.visitors; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import org.antlr.sql.sca.matchers.ClassNameMatcher; 7 | import org.antlr.sql.sca.matchers.TextMatcher; 8 | import org.antlr.sql.sca.nodes.ParseTreeNode; 9 | import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; 10 | import org.antlr.v4.runtime.tree.ParseTree; 11 | import org.sonar.plugins.sql.issues.RuleToCheck; 12 | import org.sonar.plugins.sql.issues.RuleToCheck.RuleCheckResult; 13 | import org.sonar.plugins.sql.models.rules.Rule; 14 | import org.sonar.plugins.sql.models.rules.RuleImplementation; 15 | 16 | public class RulesMatchingVisitor2 extends AbstractParseTreeVisitor { 17 | 18 | private final List rules; 19 | private final ClassNameMatcher matcher = new ClassNameMatcher(); 20 | private final TextMatcher textMatcher = new TextMatcher(); 21 | private final Map results = new LinkedHashMap<>(); 22 | 23 | public Map getResults() { 24 | return results; 25 | } 26 | 27 | public RulesMatchingVisitor2(List rules) { 28 | this.rules = rules; 29 | rules.forEach( 30 | r -> { 31 | results.put(r, new RuleCheckResult()); 32 | }); 33 | } 34 | 35 | @Override 36 | public Void visit(final ParseTree tree) { 37 | 38 | final int n = tree.getChildCount(); 39 | 40 | for (int i = 0; i < n; i++) { 41 | final ParseTree c = tree.getChild(i); 42 | visit(c); 43 | } 44 | final ParseTreeNode node = new ParseTreeNode(tree); 45 | for (RuleToCheck rules : this.rules) { 46 | Rule rule = rules.rule; 47 | RuleImplementation ruleImplementation = rule.getRuleImplementation(); 48 | 49 | if (!this.matcher.match(node, ruleImplementation) 50 | || !this.textMatcher.match(node, ruleImplementation)) { 51 | continue; 52 | } 53 | 54 | results.get(rules).candidates.add(node); 55 | 56 | // TODO: check this 57 | // if (RuleMode.GROUP.equals(ruleImplementation.getRuleMode())) { 58 | // this.groupMatches.putIfAbsent(node.getText(), match); 59 | // } else { 60 | // this.matches.add(match); 61 | // } 62 | } 63 | return defaultResult(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/Dialects.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import org.antlr.sql.dialects.rules.CommonRules; 8 | import org.antlr.sql.models.AntlrContext; 9 | import org.sonar.plugins.sql.models.rules.SqlRules; 10 | 11 | public enum Dialects { 12 | TSQL(new TSQLDialect()), 13 | PSSQL(new PsSqlDialect()), 14 | MYSQL(new MySqlDialect()), 15 | VSQL(new VSQLDialect()), 16 | PSSQLV2(new PsSqlV2Dialect()), 17 | SNOWFLAKE(new SnowflakeDialect()); 18 | 19 | public AntlrContext parse(String text) { 20 | return parse(text, Collections.emptyList()); 21 | } 22 | 23 | public List getDialectRules(SqlRules... additionalRules) { 24 | List rules = new LinkedList(); 25 | SQLDialectRules.INSTANCE 26 | .getRules() 27 | .forEach( 28 | r -> { 29 | if (r.getDialect() == null 30 | || this.name().equalsIgnoreCase(r.getDialect())) { 31 | rules.add(r); 32 | } 33 | }); 34 | 35 | rules.addAll(CommonRules.INSTANCE.getRules()); 36 | rules.addAll(Arrays.asList(additionalRules)); 37 | return rules; 38 | } 39 | 40 | public AntlrContext parseInitialContext(String text) { 41 | return this.dialect.parse(text); 42 | } 43 | 44 | public AntlrContext parse(String text, List rules) { 45 | AntlrContext ctx = this.dialect.parse(text); 46 | ctx.initialContents = text; 47 | SQLDialectRules.INSTANCE 48 | .getRules() 49 | .forEach( 50 | r -> { 51 | if (r.getDialect() == null 52 | || this.name().equalsIgnoreCase(r.getDialect())) { 53 | ctx.rules.add(r); 54 | } 55 | }); 56 | 57 | rules.forEach( 58 | r -> { 59 | if (r.getDialect() == null || this.name().equalsIgnoreCase(r.getDialect())) { 60 | ctx.rules.add(r); 61 | } 62 | }); 63 | ctx.rules.addAll(CommonRules.INSTANCE.getRules()); 64 | 65 | return ctx; 66 | } 67 | 68 | private final IDialect dialect; 69 | 70 | private Dialects(IDialect dialect) { 71 | this.dialect = dialect; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/sensors/XmlHelper.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.sensors; 2 | 3 | import java.util.function.Function; 4 | import org.w3c.dom.Node; 5 | import org.w3c.dom.NodeList; 6 | 7 | public final class XmlHelper { 8 | public static final String readAttribute(Node node, String att) { 9 | return readAttribute(node, att, null); 10 | } 11 | 12 | public static final String readAttribute(Node node, String att, String defaultValue) { 13 | if (node == null) { 14 | return null; 15 | } 16 | Node attributeNode = node.getAttributes().getNamedItem(att); 17 | if (attributeNode == null) { 18 | return defaultValue; 19 | } 20 | return attributeNode.getTextContent(); 21 | } 22 | 23 | public static final Node getNode(Node parent, String name) { 24 | NodeList children = parent.getChildNodes(); 25 | for (int i = 0; i < children.getLength(); i++) { 26 | Node child = children.item(i); 27 | if (name.equalsIgnoreCase(child.getNodeName())) { 28 | return child; 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | public static final String getNodeValue(Node parent, String name) { 35 | NodeList children = parent.getChildNodes(); 36 | for (int i = 0; i < children.getLength(); i++) { 37 | Node child = children.item(i); 38 | if (name.equalsIgnoreCase(child.getNodeName())) { 39 | return child.getTextContent(); 40 | } 41 | } 42 | return null; 43 | } 44 | 45 | public static final String getNodeValue(Node parent, String name, String defaultValue) { 46 | NodeList children = parent.getChildNodes(); 47 | for (int i = 0; i < children.getLength(); i++) { 48 | Node child = children.item(i); 49 | if (name.equalsIgnoreCase(child.getNodeName())) { 50 | return child.getTextContent(); 51 | } 52 | } 53 | return defaultValue; 54 | } 55 | 56 | public static final T getNodeValue2( 57 | Node parent, String name, Function func, T defaultValue) { 58 | NodeList children = parent.getChildNodes(); 59 | for (int i = 0; i < children.getLength(); i++) { 60 | Node child = children.item(i); 61 | if (name.equalsIgnoreCase(child.getNodeName())) { 62 | try { 63 | return func.apply(child.getTextContent()); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | return defaultValue; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/LineMetricsFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.models.AntlrContext; 5 | import org.antlr.v4.runtime.Token; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.sensor.SensorContext; 9 | import org.sonar.api.measures.CoreMetrics; 10 | import org.sonar.api.utils.log.Logger; 11 | import org.sonar.api.utils.log.Loggers; 12 | 13 | public class LineMetricsFiller implements Filler { 14 | private static final Logger LOGGER = Loggers.get(LineMetricsFiller.class); 15 | 16 | private static final int COMMENT = 1; 17 | private static final int CODE = 2; 18 | 19 | public void fill( 20 | final InputFile file, final SensorContext context, final AntlrContext antlrContext) { 21 | try { 22 | final long[] lines = new long[file.lines() + 1]; 23 | 24 | final List tokens = antlrContext.getAllTokens(); 25 | for (final Token token : tokens) { 26 | if (token.getStopIndex() <= token.getStartIndex()) { 27 | continue; 28 | } 29 | final int start = token.getLine(); 30 | final int end = start + StringUtils.countMatches(token.getText(), '\r'); 31 | final int type = antlrContext.isComment(token) ? COMMENT : CODE; 32 | 33 | for (int i = start; i <= end; i++) { 34 | lines[i] |= type; 35 | } 36 | } 37 | int commentLineCount = 0; 38 | int nonCommentLineCount = 0; 39 | for (int i = 0; i < lines.length; i++) { 40 | if ((lines[i] == COMMENT)) { 41 | commentLineCount++; 42 | continue; 43 | } 44 | if ((lines[i] & CODE) == CODE) { 45 | nonCommentLineCount++; 46 | } 47 | } 48 | synchronized (context) { 49 | context.newMeasure() 50 | .on(file) 51 | .forMetric(CoreMetrics.COMMENT_LINES) 52 | .withValue(commentLineCount) 53 | .save(); 54 | context.newMeasure() 55 | .on(file) 56 | .forMetric(CoreMetrics.NCLOC) 57 | .withValue(nonCommentLineCount) 58 | .save(); 59 | } 60 | 61 | } catch (Throwable e) { 62 | LOGGER.warn("Unexpected error adding line counts metrics on: " + file, e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/psqlv2/ParserDispatchingErrorListener.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects.psqlv2; 2 | import java.util.BitSet; 3 | 4 | import org.antlr.v4.runtime.ANTLRErrorListener; 5 | import org.antlr.v4.runtime.Parser; 6 | import org.antlr.v4.runtime.ProxyErrorListener; 7 | import org.antlr.v4.runtime.RecognitionException; 8 | import org.antlr.v4.runtime.Recognizer; 9 | import org.antlr.v4.runtime.atn.ATNConfigSet; 10 | import org.antlr.v4.runtime.dfa.DFA; 11 | 12 | public class ParserDispatchingErrorListener implements ANTLRErrorListener 13 | { 14 | Parser _parent; 15 | 16 | public ParserDispatchingErrorListener(Parser parent) 17 | { 18 | _parent = parent; 19 | } 20 | 21 | public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) 22 | { 23 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 24 | foo.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); 25 | } 26 | 27 | public void reportAmbiguity(Parser recognizer, 28 | DFA dfa, 29 | int startIndex, 30 | int stopIndex, 31 | boolean exact, 32 | BitSet ambigAlts, 33 | ATNConfigSet configs) 34 | { 35 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 36 | foo.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs); 37 | } 38 | 39 | public void reportAttemptingFullContext(Parser recognizer, 40 | DFA dfa, 41 | int startIndex, 42 | int stopIndex, 43 | BitSet conflictingAlts, 44 | ATNConfigSet configs) 45 | { 46 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 47 | foo.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs); 48 | } 49 | 50 | public void reportContextSensitivity(Parser recognizer, 51 | DFA dfa, 52 | int startIndex, 53 | int stopIndex, 54 | int prediction, 55 | ATNConfigSet configs) 56 | { 57 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 58 | foo.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/comments/CommentsGrammarListener.java: -------------------------------------------------------------------------------- 1 | // Generated from java-escape by ANTLR 4.11.1 2 | package org.antlr.sql.dialects.comments; 3 | import org.antlr.v4.runtime.tree.ParseTreeListener; 4 | 5 | /** 6 | * This interface defines a complete listener for a parse tree produced by 7 | * {@link CommentsGrammarParser}. 8 | */ 9 | public interface CommentsGrammarListener extends ParseTreeListener { 10 | /** 11 | * Enter a parse tree produced by {@link CommentsGrammarParser#root}. 12 | * @param ctx the parse tree 13 | */ 14 | void enterRoot(CommentsGrammarParser.RootContext ctx); 15 | /** 16 | * Exit a parse tree produced by {@link CommentsGrammarParser#root}. 17 | * @param ctx the parse tree 18 | */ 19 | void exitRoot(CommentsGrammarParser.RootContext ctx); 20 | /** 21 | * Enter a parse tree produced by {@link CommentsGrammarParser#statement}. 22 | * @param ctx the parse tree 23 | */ 24 | void enterStatement(CommentsGrammarParser.StatementContext ctx); 25 | /** 26 | * Exit a parse tree produced by {@link CommentsGrammarParser#statement}. 27 | * @param ctx the parse tree 28 | */ 29 | void exitStatement(CommentsGrammarParser.StatementContext ctx); 30 | /** 31 | * Enter a parse tree produced by {@link CommentsGrammarParser#comment}. 32 | * @param ctx the parse tree 33 | */ 34 | void enterComment(CommentsGrammarParser.CommentContext ctx); 35 | /** 36 | * Exit a parse tree produced by {@link CommentsGrammarParser#comment}. 37 | * @param ctx the parse tree 38 | */ 39 | void exitComment(CommentsGrammarParser.CommentContext ctx); 40 | /** 41 | * Enter a parse tree produced by {@link CommentsGrammarParser#multiline_comment}. 42 | * @param ctx the parse tree 43 | */ 44 | void enterMultiline_comment(CommentsGrammarParser.Multiline_commentContext ctx); 45 | /** 46 | * Exit a parse tree produced by {@link CommentsGrammarParser#multiline_comment}. 47 | * @param ctx the parse tree 48 | */ 49 | void exitMultiline_comment(CommentsGrammarParser.Multiline_commentContext ctx); 50 | /** 51 | * Enter a parse tree produced by {@link CommentsGrammarParser#line_comment}. 52 | * @param ctx the parse tree 53 | */ 54 | void enterLine_comment(CommentsGrammarParser.Line_commentContext ctx); 55 | /** 56 | * Exit a parse tree produced by {@link CommentsGrammarParser#line_comment}. 57 | * @param ctx the parse tree 58 | */ 59 | void exitLine_comment(CommentsGrammarParser.Line_commentContext ctx); 60 | /** 61 | * Enter a parse tree produced by {@link CommentsGrammarParser#non_comment}. 62 | * @param ctx the parse tree 63 | */ 64 | void enterNon_comment(CommentsGrammarParser.Non_commentContext ctx); 65 | /** 66 | * Exit a parse tree produced by {@link CommentsGrammarParser#non_comment}. 67 | * @param ctx the parse tree 68 | */ 69 | void exitNon_comment(CommentsGrammarParser.Non_commentContext ctx); 70 | } -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/psqlv2/LexerDispatchingErrorListener.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects.psqlv2; 2 | import java.util.BitSet; 3 | 4 | import org.antlr.v4.runtime.ANTLRErrorListener; 5 | import org.antlr.v4.runtime.Lexer; 6 | import org.antlr.v4.runtime.Parser; 7 | import org.antlr.v4.runtime.ProxyErrorListener; 8 | import org.antlr.v4.runtime.RecognitionException; 9 | import org.antlr.v4.runtime.Recognizer; 10 | import org.antlr.v4.runtime.atn.ATNConfigSet; 11 | import org.antlr.v4.runtime.dfa.DFA; 12 | 13 | public class LexerDispatchingErrorListener implements ANTLRErrorListener 14 | { 15 | Lexer _parent; 16 | 17 | public LexerDispatchingErrorListener(Lexer parent) 18 | { 19 | _parent = parent; 20 | } 21 | 22 | public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) 23 | { 24 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 25 | foo.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); 26 | } 27 | 28 | public void reportAmbiguity(Parser recognizer, 29 | DFA dfa, 30 | int startIndex, 31 | int stopIndex, 32 | boolean exact, 33 | BitSet ambigAlts, 34 | ATNConfigSet configs) 35 | { 36 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 37 | foo.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs); 38 | } 39 | 40 | public void reportAttemptingFullContext(Parser recognizer, 41 | DFA dfa, 42 | int startIndex, 43 | int stopIndex, 44 | BitSet conflictingAlts, 45 | ATNConfigSet configs) 46 | { 47 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 48 | foo.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs); 49 | } 50 | 51 | public void reportContextSensitivity(Parser recognizer, 52 | DFA dfa, 53 | int startIndex, 54 | int stopIndex, 55 | int prediction, 56 | ATNConfigSet configs) 57 | { 58 | ProxyErrorListener foo = new ProxyErrorListener(_parent.getErrorListeners()); 59 | foo.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/models/rules/RuleResultType.java: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference 3 | // Implementation, v2.2.8-b130911.1802 4 | // See http://java.sun.com/xml/jaxb 5 | // Any modifications to this file will be lost upon recompilation of the source schema. 6 | // Generated on: 2019.03.25 at 09:04:39 PM EET 7 | // 8 | 9 | package org.sonar.plugins.sql.models.rules; 10 | 11 | import javax.xml.bind.annotation.XmlEnum; 12 | import javax.xml.bind.annotation.XmlEnumValue; 13 | import javax.xml.bind.annotation.XmlType; 14 | import org.codehaus.plexus.util.StringUtils; 15 | 16 | /** 17 | * Java class for ruleResultType. 18 | * 19 | *

The following schema fragment specifies the expected content contained within this class. 20 | * 21 | *

22 | * 23 | *

24 |  * <simpleType name="ruleResultType">
25 |  *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
26 |  *     <enumeration value="Default"/>
27 |  *     <enumeration value="FailIfFound"/>
28 |  *     <enumeration value="FailIfNotFound"/>
29 |  *     <enumeration value="FailIfLessFound"/>
30 |  *     <enumeration value="FailIfMoreFound"/>
31 |  *     <enumeration value="SkipIfFound"/>
32 |  *     <enumeration value="SkipIfNotFound"/>
33 |  *   </restriction>
34 |  * </simpleType>
35 |  * 
36 | */ 37 | @XmlType(name = "ruleResultType") 38 | @XmlEnum 39 | public enum RuleResultType { 40 | @XmlEnumValue("Default") 41 | DEFAULT("Default"), 42 | @XmlEnumValue("FailIfFound") 43 | FAIL_IF_FOUND("FailIfFound"), 44 | @XmlEnumValue("FailIfNotFound") 45 | FAIL_IF_NOT_FOUND("FailIfNotFound"), 46 | @XmlEnumValue("FailIfLessFound") 47 | FAIL_IF_LESS_FOUND("FailIfLessFound"), 48 | @XmlEnumValue("FailIfMoreFound") 49 | FAIL_IF_MORE_FOUND("FailIfMoreFound"), 50 | @XmlEnumValue("SkipIfFound") 51 | SKIP_IF_FOUND("SkipIfFound"), 52 | 53 | @XmlEnumValue("SkipIfLessFound") 54 | SKIP_IF_LESS_FOUND("SkipIfLessFound"), 55 | @XmlEnumValue("SkipIfMoreFound") 56 | SKIP_IF_MORE_FOUND("FailIfMoreFound"), 57 | @XmlEnumValue("SkipIfNotFound") 58 | SKIP_IF_NOT_FOUND("SkipIfNotFound"); 59 | private final String value; 60 | 61 | RuleResultType(String v) { 62 | value = v; 63 | } 64 | 65 | public String value() { 66 | return value; 67 | } 68 | 69 | public static RuleResultType fromValue(String v) { 70 | if (StringUtils.isBlank(v)) { 71 | return RuleResultType.DEFAULT; 72 | } 73 | 74 | for (RuleResultType c : RuleResultType.values()) { 75 | if (c.value.equalsIgnoreCase(v) || c.name().equalsIgnoreCase(v)) { 76 | return c; 77 | } 78 | } 79 | throw new IllegalArgumentException(v); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/CommentIssuesFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import org.antlr.sql.dialects.comments.CommentsGrammarLexer; 6 | import org.antlr.sql.dialects.comments.CommentsGrammarParser; 7 | import org.antlr.sql.models.AntlrContext; 8 | import org.antlr.sql.sca.IssuesProvider; 9 | import org.antlr.v4.runtime.CharStream; 10 | import org.antlr.v4.runtime.CharStreams; 11 | import org.antlr.v4.runtime.CommonTokenStream; 12 | import org.antlr.v4.runtime.Lexer; 13 | import org.antlr.v4.runtime.tree.ParseTree; 14 | import org.sonar.api.batch.fs.InputFile; 15 | import org.sonar.api.batch.sensor.SensorContext; 16 | import org.sonar.api.utils.log.Logger; 17 | import org.sonar.api.utils.log.Loggers; 18 | import org.sonar.plugins.sql.CaseChangingCharStream; 19 | import org.sonar.plugins.sql.issues.RuleToCheck; 20 | import org.sonar.plugins.sql.issues.SqlIssuesList; 21 | import org.sonar.plugins.sql.models.rules.SqlRules; 22 | import org.sonar.plugins.sql.sensors.BaseSensor; 23 | 24 | public class CommentIssuesFiller extends BaseSensor implements Filler { 25 | private static final Logger LOGGER = Loggers.get(CommentIssuesFiller.class); 26 | private final IssuesProvider issuesProvider = new IssuesProvider(); 27 | 28 | @Override 29 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 30 | 31 | try { 32 | 33 | SqlIssuesList sqlIssuesList = getIssues(antlrContext); 34 | 35 | try { 36 | addIssues(context, sqlIssuesList, file); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | 41 | } catch (IOException e1) { 42 | LOGGER.warn("Unexpected error adding issues", e1); 43 | } 44 | } 45 | 46 | public SqlIssuesList getIssues(AntlrContext antlrContext) throws IOException { 47 | ParseTree root = getRoot(antlrContext); 48 | 49 | List rulesToCheck = 50 | RuleToCheck.createCommentsList(antlrContext.rules.toArray(new SqlRules[0])); 51 | 52 | SqlIssuesList sqlIssuesList = issuesProvider.check(rulesToCheck, root); 53 | return sqlIssuesList; 54 | } 55 | 56 | ParseTree getRoot(AntlrContext antlrContext) { 57 | final CharStream charStream = 58 | new CaseChangingCharStream( 59 | CharStreams.fromString(antlrContext.initialContents), true); 60 | 61 | Lexer lexer = new CommentsGrammarLexer(charStream); 62 | lexer.removeErrorListeners(); 63 | 64 | final CommonTokenStream stream = new CommonTokenStream(lexer); 65 | stream.fill(); 66 | 67 | final CommentsGrammarParser p = new CommentsGrammarParser(stream); 68 | p.removeErrorListeners(); 69 | ParseTree root = p.root(); 70 | return root; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/MsRulesDefinition.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql; 2 | 3 | import java.io.InputStream; 4 | import javax.xml.parsers.DocumentBuilder; 5 | import javax.xml.parsers.DocumentBuilderFactory; 6 | import org.sonar.api.server.rule.RulesDefinition; 7 | import org.sonar.api.utils.log.Logger; 8 | import org.sonar.api.utils.log.Loggers; 9 | import org.sonar.plugins.sql.sensors.XmlHelper; 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Node; 12 | import org.w3c.dom.NodeList; 13 | 14 | public class MsRulesDefinition implements RulesDefinition { 15 | 16 | private static final Logger LOGGER = Loggers.get(MsRulesDefinition.class); 17 | 18 | @Override 19 | public void define(Context context) { 20 | final NewRepository repository = 21 | context.createExternalRepository(Constants.TSQL_MS_ENGINEID, Constants.languageKey); 22 | try (InputStream stream = 23 | this.getClass().getClassLoader().getResourceAsStream("vssql-rules.xml")) { 24 | 25 | final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 26 | final DocumentBuilder builder = factory.newDocumentBuilder(); 27 | final Document xmlDoc = builder.parse(stream); 28 | 29 | final NodeList nodes = xmlDoc.getElementsByTagName("rule"); 30 | for (int i = 0; i < nodes.getLength(); i++) { 31 | try { 32 | final Node node = nodes.item(i); 33 | final String key = XmlHelper.getNodeValue(node, "key"); 34 | final String name = XmlHelper.getNodeValue(node, "name"); 35 | final String description = XmlHelper.getNodeValue(node, "description"); 36 | 37 | final String constantPerIssue = 38 | XmlHelper.getNodeValue(node, "debtRemediationFunctionCoefficient"); 39 | final String severity = XmlHelper.getNodeValue(node, "severity"); 40 | final String tags[] = XmlHelper.getNodeValue(node, "tag").split(","); 41 | 42 | final NewRule rule = 43 | repository 44 | .createRule(key) 45 | .setName(name) 46 | .setMarkdownDescription(description) 47 | .setSeverity(severity) 48 | .setTags(tags); 49 | rule.setDebtRemediationFunction( 50 | rule.debtRemediationFunctions().constantPerIssue(constantPerIssue)); 51 | } catch (final Exception e) { 52 | LOGGER.warn(String.format("Unexpected error while registering rule: %s", i), e); 53 | } 54 | } 55 | } catch (Exception e) { 56 | LOGGER.warn("Unexpected error while registering rules", e); 57 | } 58 | repository.done(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/antlr/sql/dialects/rules/CommonRules.java: -------------------------------------------------------------------------------- 1 | package org.antlr.sql.dialects.rules; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import org.antlr.sql.dialects.comments.CommentsGrammarParser.Multiline_commentContext; 7 | import org.sonar.plugins.sql.models.rules.Rule; 8 | import org.sonar.plugins.sql.models.rules.RuleDistanceIndexMatchType; 9 | import org.sonar.plugins.sql.models.rules.RuleMatchType; 10 | import org.sonar.plugins.sql.models.rules.RuleResultType; 11 | import org.sonar.plugins.sql.models.rules.SqlRules; 12 | 13 | public enum CommonRules { 14 | INSTANCE; 15 | 16 | public List getRules() { 17 | List rules = new ArrayList<>(); 18 | { 19 | SqlRules customRules = new SqlRules(); 20 | customRules.setRepoKey("SQLCC"); 21 | customRules.setRepoName("SQL Plugin checks"); 22 | customRules.setDialect(null); 23 | customRules.getRule().addAll(Arrays.asList(getCommentIsRequired())); 24 | rules.add(customRules); 25 | } 26 | return rules; 27 | } 28 | 29 | protected Rule getCommentIsRequired() { 30 | Rule rule = new Rule(); 31 | rule.setKey("C030"); 32 | rule.setInternalKey("C030"); 33 | rule.setName("File does not start with multiline/header comment"); 34 | rule.setDescription("File does not start with multiline/header comment."); 35 | rule.setTag("maintainability"); 36 | rule.setSeverity("MINOR"); 37 | rule.setRemediationFunction("LINEAR"); 38 | rule.setDebtRemediationFunctionCoefficient("2min"); 39 | rule.setRuleReportsOn("file"); 40 | rule.getRuleImplementation().setRuleViolationMessage("File header comment was not found"); 41 | rule.getRuleImplementation() 42 | .getNames() 43 | .getTextItem() 44 | .add(Multiline_commentContext.class.getSimpleName()); 45 | rule.getRuleImplementation().setRuleMatchType(RuleMatchType.CLASS_ONLY); 46 | rule.getRuleImplementation().setRuleResultType(RuleResultType.FAIL_IF_NOT_FOUND); 47 | rule.getRuleImplementation().setIndex(4); 48 | rule.getRuleImplementation().setIndexCheckType(RuleDistanceIndexMatchType.EQUALS); 49 | 50 | rule.setRuleAppliesTo("comments"); 51 | 52 | rule.getRuleImplementation() 53 | .getCompliantRulesCodeExamples() 54 | .getRuleCodeExample() 55 | .add("/* AUTHOR: test\r\nDate: 2020-01-01\r\n */\r\n SELECT * FROM test_table1;"); 56 | rule.getRuleImplementation() 57 | .getViolatingRulesCodeExamples() 58 | .getRuleCodeExample() 59 | .add("SELECT * FROM test_table1;"); 60 | rule.getRuleImplementation() 61 | .getViolatingRulesCodeExamples() 62 | .getRuleCodeExample() 63 | .add("SELECT * FROM test_table1; /*additionalComment*/ "); 64 | return rule; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/main/java/org/sonar/plugins/sql/fillers/HighlighterFiller.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.fillers; 2 | 3 | import java.util.List; 4 | import org.antlr.sql.models.AntlrContext; 5 | import org.antlr.v4.runtime.Token; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.sonar.api.batch.fs.InputFile; 8 | import org.sonar.api.batch.fs.TextRange; 9 | import org.sonar.api.batch.sensor.SensorContext; 10 | import org.sonar.api.batch.sensor.highlighting.NewHighlighting; 11 | import org.sonar.api.batch.sensor.highlighting.TypeOfText; 12 | import org.sonar.api.utils.log.Logger; 13 | import org.sonar.api.utils.log.Loggers; 14 | 15 | public class HighlighterFiller implements Filler { 16 | 17 | private static final Logger LOGGER = Loggers.get(HighlighterFiller.class); 18 | 19 | @Override 20 | public void fill(InputFile file, SensorContext context, AntlrContext antlrContext) { 21 | try { 22 | NewHighlighting newHighlighting = context.newHighlighting().onFile(file); 23 | List tokens = antlrContext.getAllTokens(); 24 | for (Token token : tokens) { 25 | try { 26 | 27 | final String text = token.getText(); 28 | if (token.getType() == -1 29 | || token.getStartIndex() >= token.getStopIndex() 30 | || StringUtils.isEmpty(text)) { 31 | continue; 32 | } 33 | final TextRange range = 34 | file.newRange( 35 | token.getLine(), 36 | token.getCharPositionInLine(), 37 | token.getLine(), 38 | token.getCharPositionInLine() + text.length()); 39 | if (antlrContext.isComment(token)) { 40 | newHighlighting.highlight(range, TypeOfText.COMMENT); 41 | continue; 42 | } 43 | if (antlrContext.isString(token)) { 44 | newHighlighting.highlight(range, TypeOfText.STRING); 45 | continue; 46 | } 47 | if (antlrContext.isKeyword(token)) { 48 | newHighlighting.highlight(range, TypeOfText.KEYWORD); 49 | continue; 50 | } 51 | } catch (Exception e) { 52 | if (LOGGER.isDebugEnabled()) { 53 | LOGGER.debug( 54 | "Cannot add highlighting: {} on file {} for token {}", 55 | e, 56 | file, 57 | token); 58 | } 59 | } 60 | } 61 | synchronized (context) { 62 | newHighlighting.save(); 63 | } 64 | } catch (Throwable e) { 65 | LOGGER.warn("Unexpected error adding highlighting on: " + file, e); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/java/org/sonar/plugins/sql/sensors/MSIssuesSensorTest.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.sql.sensors; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.Charset; 6 | import java.nio.file.Files; 7 | import org.apache.commons.io.FileUtils; 8 | import org.junit.Assert; 9 | import org.junit.Assume; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | import org.junit.rules.TemporaryFolder; 13 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 14 | import org.sonar.api.batch.fs.internal.TestInputFileBuilder; 15 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 16 | import org.sonar.api.impl.utils.JUnitTempFolder; 17 | import org.sonar.api.utils.log.LogTester; 18 | import org.sonar.plugins.sql.Constants; 19 | 20 | public class MSIssuesSensorTest { 21 | 22 | @Rule public TemporaryFolder folder = new TemporaryFolder(); 23 | 24 | @Rule public LogTester logTester = new LogTester(); 25 | 26 | @Rule public JUnitTempFolder temp = new org.sonar.api.impl.utils.JUnitTempFolder(); 27 | 28 | @Test 29 | public void testExecute() throws IOException { 30 | Assume.assumeFalse( 31 | "OS not mac", System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0); 32 | 33 | SensorContextTester ctxTester = SensorContextTester.create(folder.getRoot()); 34 | ctxTester.fileSystem().setWorkDir(folder.getRoot().toPath()); 35 | 36 | File baseFile = folder.newFile("sample2.sql"); 37 | 38 | File resFile = folder.newFile("staticcodeanalysis.results.xml"); 39 | 40 | FileUtils.write( 41 | resFile, 42 | "\r\n" 43 | + "" 44 | + "" 45 | + "1" 46 | + "S1" 47 | + "Test" 48 | + "MAJOR" 49 | + "" 50 | + baseFile.getAbsolutePath() 51 | + "" 52 | + "", 53 | Charset.defaultCharset()); 54 | 55 | FileUtils.copyURLToFile(getClass().getResource("/tsql/sample2.sql"), baseFile); 56 | String contents = new String(Files.readAllBytes(baseFile.toPath())); 57 | 58 | DefaultInputFile ti = 59 | new TestInputFileBuilder("test", folder.getRoot(), baseFile) 60 | .initMetadata(contents) 61 | .setLanguage(Constants.languageKey) 62 | .setContents(contents) 63 | .build(); 64 | ctxTester.fileSystem().add(ti); 65 | MSIssuesSensor s = new MSIssuesSensor(); 66 | s.execute(ctxTester); 67 | Assert.assertEquals(1, ctxTester.allExternalIssues().size()); 68 | Assert.assertEquals(0, ctxTester.allIssues().size()); 69 | Assert.assertEquals(0, ctxTester.allAdHocRules().size()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/external/antlr4-grammar-sql-comments/src/test/java/comments/MainTest.java: -------------------------------------------------------------------------------- 1 | package comments; 2 | 3 | import org.antlr.v4.runtime.BaseErrorListener; 4 | import org.antlr.v4.runtime.CharStreams; 5 | import org.antlr.v4.runtime.CodePointCharStream; 6 | import org.antlr.v4.runtime.CommonTokenStream; 7 | import org.antlr.v4.runtime.RecognitionException; 8 | import org.antlr.v4.runtime.Recognizer; 9 | import org.antlr.v4.runtime.Token; 10 | import org.antlr.v4.runtime.misc.Interval; 11 | import org.antlr.v4.runtime.tree.ParseTree; 12 | import org.antlr.sql.comments.CommentsGrammarLexer; 13 | import org.antlr.sql.comments.CommentsGrammarParser; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.junit.Test; 16 | 17 | public class MainTest { 18 | 19 | @Test 20 | public void test() { 21 | String text = "/* aaa */\r\n//testas;//tst\r\nselect 1,\"aa\";/*inline*/;;insert 2"; 22 | text = "SELECT/*aa\r\na*/ insert 2\r\n//testas\r\n--testas2\r\n"; 23 | System.out.println(text); 24 | CodePointCharStream textStream = CharStreams.fromString(text.toUpperCase()); 25 | CommentsGrammarLexer lexer = new CommentsGrammarLexer(textStream); 26 | CommonTokenStream stream = new CommonTokenStream(lexer); 27 | CommentsGrammarParser parser = new CommentsGrammarParser(stream); 28 | parser.addErrorListener(new BaseErrorListener() { 29 | @Override 30 | public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, 31 | int charPositionInLine, String msg, RecognitionException e) throws RecognitionException { 32 | System.out.println("ERROR " + msg); 33 | } 34 | }); 35 | ParseTree root = parser.root(); 36 | print(root, 1, stream); 37 | 38 | } 39 | 40 | public static void print(final ParseTree node, final int level, CommonTokenStream stream) { 41 | final Interval sourceInterval = node.getSourceInterval(); 42 | int line = -1; 43 | int charStart = -1; 44 | int endLine = -1; 45 | int endChar = -1; 46 | String text = null; 47 | try { 48 | final Token firstToken = stream.get(sourceInterval.a); 49 | line = firstToken.getLine(); 50 | charStart = firstToken.getCharPositionInLine(); 51 | endLine = line; 52 | endChar = charStart + firstToken.getText().length(); 53 | text = firstToken.getText(); 54 | } catch (Exception e) { 55 | 56 | } 57 | 58 | String data = "@(" + line + ":" + charStart + "," + endLine + ":" + endChar + ") with text: " + text; 59 | final int tmp = level + 1; 60 | final StringBuilder sb = new StringBuilder(); 61 | sb.append(StringUtils.repeat("\t", level)); 62 | sb.append(node.getClass().getSimpleName() + ": " + data + " :" + node.getText()); 63 | System.out.println(sb.toString()); 64 | final int n = node.getChildCount(); 65 | for (int i = 0; i < n; i++) { 66 | final ParseTree c = node.getChild(i); 67 | print(c, tmp, stream); 68 | 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/sonar-sql-plugin/src/test/resources/external/rule2.customRules: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C002 6 | DEMO: SELECT * is used 7 | C002 8 | <h2>Description</h2><p>SELECT * is used. Please list names.</p><h2>Code examples</h2><h3>Non-compliant</h3><pre><code>SELECT * from dbo.test;</code></pre><h3>Compliant</h3><pre><code>SELECT name, surname from dbo.test;</code></pre><pre><code>SELECT name, surname, 1 * 3 from dbo.test;</code></pre> 9 | MINOR 10 | LINEAR 11 | 2min 12 | design 13 | 14 | 15 | Select_list_elemContext 16 | Select_list_elemContext2 17 | 18 | 19 | * 20 | 21 | 22 | 23 | test 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | msg 46 | 4 47 | 2 48 | 3 49 | Less 50 | Equals 51 | Group 52 | TextAndClass 53 | FailIfFound 54 | Strict 55 | 56 | SELECT * from dbo.test; 57 | 58 | 59 | SELECT name, surname from dbo.test; 60 | SELECT name, surname, 1 * 3 from dbo.test; 61 | 62 | 63 | 64 | 65 | 66 | --------------------------------------------------------------------------------