From 4a4554bac9807a389314f5b3c1ab011525bbe49a Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Mon, 15 Jan 2024 15:32:58 -0500 Subject: [PATCH] @W-14689540@: (Part 6) Analyze results, set outputs, and create violation objects --- __tests__/data/sampleRunDfaResults.json | 1 + __tests__/data/sampleRunResults.json | 1 + __tests__/fakes.ts | 56 ++++ __tests__/main.test.ts | 69 +++-- __tests__/results.test.ts | 335 ++++++++++++++++++++++++ __tests__/utils.test.ts | 13 +- dist/index.js | 317 +++++++++++++++++++++- src/index.ts | 4 +- src/main.ts | 22 +- src/results.ts | 300 +++++++++++++++++++++ src/utils.ts | 6 + 11 files changed, 1100 insertions(+), 24 deletions(-) create mode 100644 __tests__/data/sampleRunDfaResults.json create mode 100644 __tests__/data/sampleRunResults.json create mode 100644 __tests__/results.test.ts create mode 100644 src/results.ts diff --git a/__tests__/data/sampleRunDfaResults.json b/__tests__/data/sampleRunDfaResults.json new file mode 100644 index 0000000..c0aa5e9 --- /dev/null +++ b/__tests__/data/sampleRunDfaResults.json @@ -0,0 +1 @@ +[{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SampleController.cls","violations":[{"ruleName":"ApexFlsViolationRule","severity":1,"message":"FLS validation is missing for [READ] operation on [Account] with field(s) [CreatedDate,Name].","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":29,"sinkColumn":30,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SampleController.cls","sourceLine":28,"sourceColumn":33,"sourceType":"SampleController","sourceMethodName":"getAccts","normalizedSeverity":1},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"FLS validation is missing for [READ] operation on [testObj__c] with field(s) [checkbox__c,datetime__c,Name,richtext__c].","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":22,"sinkColumn":33,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SampleController.cls","sourceLine":21,"sourceColumn":36,"sourceType":"SampleController","sourceMethodName":"getObjs","normalizedSeverity":1},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"FLS validation is missing for [READ] operation on [Account] with field(s) [Name,Phone].","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":11,"sinkColumn":34,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SampleController.cls","sourceLine":4,"sourceColumn":39,"sourceType":"SampleController","sourceMethodName":"getSimpleAccounts","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/NameController.cls","violations":[{"ruleName":"InternalExecutionError","severity":3,"message":"Graph Engine identified your source and sink, but you must manually verify that you have a sanitizer in this path. Then, add an engine directive to skip the path. Next, create a Github issue for the Code Analyzer team that includes the error and stack trace. After we fix this issue, check the Code Analyzer release notes for more info. Error and stacktrace: UnexpectedException: ArrayLoadExpression{properties={FirstChild=true, BeginLine=12, DefiningType_CaseSafe=namecontroller, LastChild=false, DefiningType=NameController, EndLine=12, childIdx=0, BeginColumn=23}}: com.salesforce.graph.symbols.PathScopeVisitor.afterVisit(PathScopeVisitor.java:761);com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor.afterVisit(DefaultSymbolProviderVertexVisitor.java:737);com.salesforce.graph.vertex.ArrayLoadExpressionVertex.afterVisit(ArrayLoadExpressionVertex.java:58);com.salesforce.graph.ops.expander.ApexPathExpander.performAfterVisit(ApexPathExpander.java:577);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:536);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:523)","category":"InternalExecutionError","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":null,"sinkColumn":null,"sinkFileName":"","sourceLine":10,"sourceColumn":24,"sourceType":"NameController","sourceMethodName":"setName","normalizedSeverity":3},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":5,"sourceColumn":26,"sourceType":"NameController","sourceMethodName":"getId","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":5,"sourceColumn":26,"sourceType":"NameController","sourceMethodName":"getId","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeNamespaceAccessible.cls","violations":[{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":7,"sourceColumn":22,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"getName","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":7,"sourceColumn":22,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"getName","normalizedSeverity":1},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":12,"sourceColumn":22,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"name2","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":12,"sourceColumn":22,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"name2","normalizedSeverity":1},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":23,"sourceColumn":15,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"name3","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":23,"sourceColumn":15,"sourceType":"UnsafeNamespaceAccessible","sourceMethodName":"name3","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testSELECT2.cls","violations":[{"ruleName":"InternalExecutionError","severity":3,"message":"Graph Engine identified your source and sink, but you must manually verify that you have a sanitizer in this path. Then, add an engine directive to skip the path. Next, create a Github issue for the Code Analyzer team that includes the error and stack trace. After we fix this issue, check the Code Analyzer release notes for more info. Error and stacktrace: UnexpectedException: ArrayLoadExpression{properties={FirstChild=true, BeginLine=13, DefiningType_CaseSafe=testselect2, LastChild=true, DefiningType=testSELECT2, EndLine=14, childIdx=0, BeginColumn=16}}: com.salesforce.graph.symbols.PathScopeVisitor.afterVisit(PathScopeVisitor.java:761);com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor.afterVisit(DefaultSymbolProviderVertexVisitor.java:737);com.salesforce.graph.vertex.ArrayLoadExpressionVertex.afterVisit(ArrayLoadExpressionVertex.java:58);com.salesforce.graph.ops.expander.ApexPathExpander.performAfterVisit(ApexPathExpander.java:577);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:536);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:523)","category":"InternalExecutionError","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":null,"sinkColumn":null,"sinkFileName":"","sourceLine":11,"sourceColumn":19,"sourceType":"testSELECT2","sourceMethodName":"getName","normalizedSeverity":3}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testSELECT.cls","violations":[{"ruleName":"ApexFlsViolationRule","severity":1,"message":"FLS validation is missing for [READ] operation on [Account] with field(s) [Name].","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":18,"sinkColumn":16,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testSELECT.cls","sourceLine":16,"sourceColumn":19,"sourceType":"testSELECT","sourceMethodName":"getName","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeWebService.cls","violations":[{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":3,"sourceColumn":26,"sourceType":"UnsafeWebService","sourceMethodName":"getName","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":3,"sourceColumn":26,"sourceType":"UnsafeWebService","sourceMethodName":"getName","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeGlobal.cls","violations":[{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":5,"sourceColumn":22,"sourceType":"UnsafeGlobal","sourceMethodName":"getName","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":5,"sourceColumn":22,"sourceType":"UnsafeGlobal","sourceMethodName":"getName","normalizedSeverity":1},{"ruleName":"ApexFlsViolationRule","severity":1,"message":"Salesforce Graph Engine couldn't resolve the parameter passed to [READ] operation with field(s) [Unknown]. Confirm that this operation has the necessary FLS checks.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":19,"sourceColumn":15,"sourceType":"UnsafeGlobal","sourceMethodName":"name3","normalizedSeverity":1},{"ruleName":"UseWithSharingOnDatabaseOperation","severity":1,"message":"Database operation must be executed from a class that enforces sharing rules.","category":"Security","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation","sinkLine":4,"sinkColumn":42,"sinkFileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","sourceLine":19,"sourceColumn":15,"sourceType":"UnsafeGlobal","sourceMethodName":"name3","normalizedSeverity":1}]},{"engine":"sfge","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SharingInnerClass.cls","violations":[{"ruleName":"InternalExecutionError","severity":3,"message":"Graph Engine identified your source and sink, but you must manually verify that you have a sanitizer in this path. Then, add an engine directive to skip the path. Next, create a Github issue for the Code Analyzer team that includes the error and stack trace. After we fix this issue, check the Code Analyzer release notes for more info. Error and stacktrace: UnexpectedException: ArrayLoadExpression{properties={FirstChild=false, BeginLine=10, DefiningType_CaseSafe=sharinginnerclass.innerclass, LastChild=true, DefiningType=SharingInnerClass.InnerClass, EndLine=10, childIdx=1, BeginColumn=24}}: com.salesforce.graph.symbols.PathScopeVisitor.afterVisit(PathScopeVisitor.java:761);com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor.afterVisit(DefaultSymbolProviderVertexVisitor.java:737);com.salesforce.graph.vertex.ArrayLoadExpressionVertex.afterVisit(ArrayLoadExpressionVertex.java:58);com.salesforce.graph.ops.expander.ApexPathExpander.performAfterVisit(ApexPathExpander.java:577);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:536);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:523)","category":"InternalExecutionError","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":null,"sinkColumn":null,"sinkFileName":"","sourceLine":13,"sourceColumn":24,"sourceType":"SharingInnerClass.InnerClass","sourceMethodName":"getAccount","normalizedSeverity":3},{"ruleName":"InternalExecutionError","severity":3,"message":"Graph Engine identified your source and sink, but you must manually verify that you have a sanitizer in this path. Then, add an engine directive to skip the path. Next, create a Github issue for the Code Analyzer team that includes the error and stack trace. After we fix this issue, check the Code Analyzer release notes for more info. Error and stacktrace: UnexpectedException: ArrayLoadExpression{properties={FirstChild=false, BeginLine=10, DefiningType_CaseSafe=sharinginnerclass.innerclass, LastChild=true, DefiningType=SharingInnerClass.InnerClass, EndLine=10, childIdx=1, BeginColumn=24}}: com.salesforce.graph.symbols.PathScopeVisitor.afterVisit(PathScopeVisitor.java:761);com.salesforce.graph.symbols.DefaultSymbolProviderVertexVisitor.afterVisit(DefaultSymbolProviderVertexVisitor.java:737);com.salesforce.graph.vertex.ArrayLoadExpressionVertex.afterVisit(ArrayLoadExpressionVertex.java:58);com.salesforce.graph.ops.expander.ApexPathExpander.performAfterVisit(ApexPathExpander.java:577);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:536);com.salesforce.graph.ops.expander.ApexPathExpander.visit(ApexPathExpander.java:523)","category":"InternalExecutionError","url":"https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#ApexFlsViolationRule","sinkLine":null,"sinkColumn":null,"sinkFileName":"","sourceLine":9,"sourceColumn":16,"sourceType":"SharingInnerClass.InnerClass","sourceMethodName":"","normalizedSeverity":3}]}] \ No newline at end of file diff --git a/__tests__/data/sampleRunResults.json b/__tests__/data/sampleRunResults.json new file mode 100644 index 0000000..e8fe623 --- /dev/null +++ b/__tests__/data/sampleRunResults.json @@ -0,0 +1 @@ +[{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/NameController.cls","violations":[{"line":1,"column":14,"endLine":1,"endColumn":27,"severity":3,"ruleName":"ApexSharingViolations","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexsharingviolations","message":"\nApex classes should declare a sharing model if DML or SOQL/SOSL is used\n","normalizedSeverity":3},{"line":1,"column":14,"endLine":16,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":5,"column":26,"endLine":7,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":10,"column":24,"endLine":15,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":11,"column":12,"endLine":11,"endColumn":17,"severity":1,"ruleName":"VariableNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#variablenamingconventions","message":"\nOnly variables that are final should contain underscores (except for underscores in standard prefix/suffix), 'obj_id' is not final.\n","normalizedSeverity":1},{"line":11,"column":12,"endLine":11,"endColumn":17,"severity":1,"ruleName":"LocalVariableNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#localvariablenamingconventions","message":"\nThe local variable name 'obj_id' doesn't match '[a-z][a-zA-Z0-9]*'\n","normalizedSeverity":1},{"line":14,"column":9,"endLine":14,"endColumn":19,"severity":3,"ruleName":"ApexCRUDViolation","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexcrudviolation","message":"\nValidate CRUD permission before SOQL/DML operation or enforce user mode\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SafeNoSharing.cls","violations":[{"line":1,"column":30,"endLine":12,"endColumn":1,"severity":3,"ruleName":"AvoidGlobalModifier","category":"Best Practices","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_bestpractices.html#avoidglobalmodifier","message":"\nAvoid using global modifier\n","normalizedSeverity":3},{"line":1,"column":30,"endLine":12,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":3,"column":12,"endLine":5,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":7,"column":19,"endLine":9,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SampleController.cls","violations":[{"line":1,"column":27,"endLine":34,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":4,"column":39,"endLine":17,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":11,"column":34,"endLine":11,"endColumn":78,"severity":3,"ruleName":"ApexCRUDViolation","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexcrudviolation","message":"\nValidate CRUD permission before SOQL/DML operation or enforce user mode\n","normalizedSeverity":3},{"line":21,"column":36,"endLine":24,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":22,"column":33,"endLine":22,"endColumn":108,"severity":3,"ruleName":"ApexCRUDViolation","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexcrudviolation","message":"\nValidate CRUD permission before SOQL/DML operation or enforce user mode\n","normalizedSeverity":3},{"line":28,"column":33,"endLine":31,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":29,"column":30,"endLine":29,"endColumn":80,"severity":3,"ruleName":"ApexCRUDViolation","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexcrudviolation","message":"\nValidate CRUD permission before SOQL/DML operation or enforce user mode\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SharingInnerClass.cls","violations":[{"line":1,"column":27,"endLine":17,"endColumn":1,"severity":3,"ruleName":"AvoidGlobalModifier","category":"Best Practices","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_bestpractices.html#avoidglobalmodifier","message":"\nAvoid using global modifier\n","normalizedSeverity":3},{"line":1,"column":27,"endLine":17,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":2,"column":12,"endLine":2,"endColumn":28,"severity":3,"ruleName":"EmptyStatementBlock","category":"Error Prone","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_errorprone.html#emptystatementblock","message":"\nAvoid empty block statements.\n","normalizedSeverity":3},{"line":2,"column":12,"endLine":4,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":6,"column":18,"endLine":6,"endColumn":27,"severity":3,"ruleName":"ApexSharingViolations","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexsharingviolations","message":"\nApex classes should declare a sharing model if DML or SOQL/SOSL is used\n","normalizedSeverity":3},{"line":6,"column":18,"endLine":15,"endColumn":9,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":9,"column":16,"endLine":11,"endColumn":9,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":13,"column":24,"endLine":15,"endColumn":9,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/SimpleAccount.cls","violations":[{"line":1,"column":14,"endLine":17,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":3,"column":32,"endLine":3,"endColumn":33,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":3,"column":32,"endLine":3,"endColumn":33,"severity":1,"ruleName":"PropertyNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#propertynamingconventions","message":"\nThe instance property name 'Id' doesn't match '[a-z][a-zA-Z0-9]*'\n","normalizedSeverity":1},{"line":3,"column":32,"endLine":3,"endColumn":33,"severity":1,"ruleName":"VariableNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#variablenamingconventions","message":"\nVariables should start with a lowercase character, 'Id' starts with uppercase character.\n","normalizedSeverity":1},{"line":4,"column":32,"endLine":4,"endColumn":35,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":4,"column":32,"endLine":4,"endColumn":35,"severity":1,"ruleName":"PropertyNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#propertynamingconventions","message":"\nThe instance property name 'Name' doesn't match '[a-z][a-zA-Z0-9]*'\n","normalizedSeverity":1},{"line":4,"column":32,"endLine":4,"endColumn":35,"severity":1,"ruleName":"VariableNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#variablenamingconventions","message":"\nVariables should start with a lowercase character, 'Name' starts with uppercase character.\n","normalizedSeverity":1},{"line":5,"column":19,"endLine":5,"endColumn":23,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":5,"column":19,"endLine":5,"endColumn":23,"severity":1,"ruleName":"PropertyNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#propertynamingconventions","message":"\nThe instance property name 'Phone' doesn't match '[a-z][a-zA-Z0-9]*'\n","normalizedSeverity":1},{"line":5,"column":19,"endLine":5,"endColumn":23,"severity":1,"ruleName":"VariableNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#variablenamingconventions","message":"\nVariables should start with a lowercase character, 'Phone' starts with uppercase character.\n","normalizedSeverity":1},{"line":8,"column":12,"endLine":12,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":15,"column":12,"endLine":15,"endColumn":24,"severity":3,"ruleName":"EmptyStatementBlock","category":"Error Prone","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_errorprone.html#emptystatementblock","message":"\nAvoid empty block statements.\n","normalizedSeverity":3},{"line":15,"column":12,"endLine":15,"endColumn":29,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeGlobal.cls","violations":[{"line":1,"column":14,"endLine":22,"endColumn":1,"severity":3,"ruleName":"AvoidGlobalModifier","category":"Best Practices","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_bestpractices.html#avoidglobalmodifier","message":"\nAvoid using global modifier\n","normalizedSeverity":3},{"line":1,"column":14,"endLine":22,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":5,"column":22,"endLine":7,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":9,"column":22,"endLine":11,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":13,"column":13,"endLine":16,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":19,"column":15,"endLine":21,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeNamespaceAccessible.cls","violations":[{"line":2,"column":14,"endLine":26,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":7,"column":22,"endLine":9,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":12,"column":22,"endLine":14,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":17,"column":13,"endLine":20,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":23,"column":15,"endLine":25,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls","violations":[{"line":1,"column":14,"endLine":1,"endColumn":23,"severity":3,"ruleName":"ApexSharingViolations","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexsharingviolations","message":"\nApex classes should declare a sharing model if DML or SOQL/SOSL is used\n","normalizedSeverity":3},{"line":1,"column":14,"endLine":7,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":3,"column":26,"endLine":6,"endColumn":9,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":4,"column":95,"endLine":4,"endColumn":98,"severity":3,"ruleName":"ApexSOQLInjection","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexsoqlinjection","message":"\nAvoid untrusted/unescaped variables in DML query\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeWebService.cls","violations":[{"line":1,"column":14,"endLine":7,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":3,"column":26,"endLine":5,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testCrud1.cls","violations":[{"line":1,"column":27,"endLine":5,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":1,"column":27,"endLine":5,"endColumn":1,"severity":1,"ruleName":"ClassNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#classnamingconventions","message":"\nThe class name 'testCrud1' doesn't match '[A-Z][a-zA-Z0-9_]*'\n","normalizedSeverity":1},{"line":2,"column":9,"endLine":2,"endColumn":17,"severity":3,"ruleName":"EmptyStatementBlock","category":"Error Prone","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_errorprone.html#emptystatementblock","message":"\nAvoid empty block statements.\n","normalizedSeverity":3},{"line":2,"column":9,"endLine":4,"endColumn":2,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testSELECT.cls","violations":[{"line":1,"column":27,"endLine":22,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":1,"column":27,"endLine":22,"endColumn":1,"severity":1,"ruleName":"ClassNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#classnamingconventions","message":"\nThe class name 'testSELECT' doesn't match '[A-Z][a-zA-Z0-9_]*'\n","normalizedSeverity":1},{"line":3,"column":19,"endLine":3,"endColumn":20,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":5,"column":12,"endLine":12,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":8,"column":18,"endLine":8,"endColumn":34,"severity":3,"ruleName":"AvoidHardcodingId","category":"Error Prone","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_errorprone.html#avoidhardcodingid","message":"\nHardcoding Ids is bound to break when changing environments.\n","normalizedSeverity":3},{"line":10,"column":20,"endLine":10,"endColumn":21,"severity":3,"ruleName":"ApexXSSFromURLParam","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_security.html#apexxssfromurlparam","message":"\nApex classes should escape/sanitize Strings obtained from URL parameters\n","normalizedSeverity":3},{"line":16,"column":19,"endLine":20,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/testSELECT2.cls","violations":[{"line":1,"column":27,"endLine":16,"endColumn":1,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":1,"column":27,"endLine":16,"endColumn":1,"severity":1,"ruleName":"ClassNamingConventions","category":"Code Style","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_codestyle.html#classnamingconventions","message":"\nThe class name 'testSELECT2' doesn't match '[A-Z][a-zA-Z0-9_]*'\n","normalizedSeverity":1},{"line":2,"column":19,"endLine":2,"endColumn":20,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":4,"column":12,"endLine":7,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3},{"line":11,"column":19,"endLine":15,"endColumn":5,"severity":3,"ruleName":"ApexDoc","category":"Documentation","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_apex_documentation.html#apexdoc","message":"\nMissing ApexDoc comment\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/components/child1.component","violations":[{"line":10,"column":7,"endLine":10,"endColumn":10,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/pages/testEncoding.page","violations":[{"line":5,"column":6,"endLine":5,"endColumn":12,"severity":3,"ruleName":"VfHtmlStyleTagXss","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfhtmlstyletagxss","message":"\nDynamic EL content in style tag should be appropriately encoded\n","normalizedSeverity":3},{"line":7,"column":20,"endLine":7,"endColumn":26,"severity":3,"ruleName":"VfHtmlStyleTagXss","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfhtmlstyletagxss","message":"\nDynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate\n","normalizedSeverity":3},{"line":8,"column":20,"endLine":8,"endColumn":36,"severity":3,"ruleName":"VfHtmlStyleTagXss","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfhtmlstyletagxss","message":"\nDynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate\n","normalizedSeverity":3},{"line":9,"column":20,"endLine":9,"endColumn":38,"severity":3,"ruleName":"VfHtmlStyleTagXss","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfhtmlstyletagxss","message":"\nDynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate\n","normalizedSeverity":3},{"line":15,"column":56,"endLine":15,"endColumn":62,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":19,"column":15,"endLine":19,"endColumn":21,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":21,"column":15,"endLine":21,"endColumn":33,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/pages/testObjStandard.page","violations":[{"line":6,"column":26,"endLine":6,"endColumn":43,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":10,"column":26,"endLine":10,"endColumn":50,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":12,"column":26,"endLine":12,"endColumn":50,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":14,"column":26,"endLine":14,"endColumn":50,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3},{"line":20,"column":16,"endLine":20,"endColumn":94,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/pages/testSELECT.page","violations":[{"line":2,"column":26,"endLine":2,"endColumn":32,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3}]},{"engine":"pmd","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/pages/testSELECT2.page","violations":[{"line":2,"column":27,"endLine":2,"endColumn":33,"severity":3,"ruleName":"VfUnescapeEl","category":"Security","url":"https://pmd.github.io/pmd-6.55.0/pmd_rules_vf_security.html#vfunescapeel","message":"\nAvoid unescaped user controlled content in EL\n","normalizedSeverity":3}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/src/js/src/simpleYetWrong.js","violations":[{"line":14,"column":3,"severity":2,"message":"Unreachable code.","ruleName":"no-unreachable","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unreachable","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/AccountRepeat/AccountRepeatController.js","violations":[{"line":10,"column":9,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1},{"line":20,"column":9,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1},{"line":30,"column":9,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/CSPattr/CSPattrController.js","violations":[{"line":2,"column":22,"severity":2,"message":"'component' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":33,"severity":2,"message":"'event' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":40,"severity":2,"message":"'helper' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/ChildComponent/ChildComponentController.js","violations":[{"line":2,"column":37,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":3,"column":13,"severity":2,"message":"'evt' is already defined.","ruleName":"no-redeclare","category":"suggestion","url":"https://eslint.org/docs/latest/rules/no-redeclare","normalizedSeverity":1},{"line":8,"column":39,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":9,"column":13,"severity":2,"message":"'evt' is already defined.","ruleName":"no-redeclare","category":"suggestion","url":"https://eslint.org/docs/latest/rules/no-redeclare","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/ControllerDemo/ControllerDemoController.js","violations":[{"line":2,"column":26,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":31,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":14,"column":9,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1},{"line":16,"column":31,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":16,"column":36,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":29,"column":9,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/DOMXSS/DOMXSSController.js","violations":[{"line":2,"column":27,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":32,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":5,"column":34,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":5,"column":39,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":8,"column":32,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":8,"column":36,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":12,"column":43,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":12,"column":47,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":17,"column":43,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":17,"column":47,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":24,"column":37,"severity":2,"message":"'evt' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":24,"column":41,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":28,"column":37,"severity":2,"message":"'hlp' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/VulnTestApp/VulnTestAppController.js","violations":[{"line":2,"column":33,"severity":2,"message":"'event' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":40,"severity":2,"message":"'helper' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/VulnTest2/VulnTest2Controller.js","violations":[{"line":2,"column":22,"severity":2,"message":"'component' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":33,"severity":2,"message":"'event' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":40,"severity":2,"message":"'helper' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/doc_write/doc_writeController.js","violations":[{"line":2,"column":22,"severity":2,"message":"'component' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":33,"severity":2,"message":"'event' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":40,"severity":2,"message":"'helper' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":3,"column":19,"severity":2,"message":"'$A' is not defined.","ruleName":"no-undef","category":"problem","url":"https://eslint.org/docs/latest/rules/no-undef","normalizedSeverity":1}]},{"engine":"eslint","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/dom_parser/dom_parserController.js","violations":[{"line":2,"column":32,"severity":2,"message":"'event' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":2,"column":39,"severity":2,"message":"'helper' is defined but never used.","ruleName":"no-unused-vars","category":"problem","url":"https://eslint.org/docs/latest/rules/no-unused-vars","normalizedSeverity":1},{"line":17,"column":9,"severity":2,"message":"Unexpected 'debugger' statement.","ruleName":"no-debugger","category":"problem","url":"https://eslint.org/docs/latest/rules/no-debugger","normalizedSeverity":1},{"line":20,"column":23,"severity":2,"message":"Empty block statement.","ruleName":"no-empty","category":"suggestion","url":"https://eslint.org/docs/latest/rules/no-empty","normalizedSeverity":1}]},{"engine":"eslint-typescript","fileName":"/Users/runner/work/sample-sf-project/sample-sf-project/src/ts/src/simpleYetWrong.ts","violations":[{"line":0,"column":0,"severity":2,"message":"'/Users/runner/work/sample-sf-project/sample-sf-project/src/ts/src/simpleYetWrong.ts' doesn't reside in a location that is included by your tsconfig.json 'include' attribute.","ruleName":null,"category":"problem","url":"","exception":true,"normalizedSeverity":1}]}] \ No newline at end of file diff --git a/__tests__/fakes.ts b/__tests__/fakes.ts index b4c2cb1..2542401 100644 --- a/__tests__/fakes.ts +++ b/__tests__/fakes.ts @@ -1,6 +1,7 @@ import { CommandOutput, Dependencies } from '../src/dependencies' import { EnvironmentVariables, Inputs } from '../src/types' import { CommandExecutor } from '../src/commands' +import { Results, ResultsFactory, Violation, ViolationLocation } from '../src/results' export class FakeDependencies implements Dependencies { startGroupCallHistory: { name: string }[] = [] @@ -102,3 +103,58 @@ export class FakeCommandExecutor implements CommandExecutor { return this.runCodeAnalyzerReturnValue } } + +export class FakeResultsFactory implements ResultsFactory { + createResultsReturnValue: Results = new FakeResults() + createResultsCallHistory: { resultsFile: string; isDfa: boolean }[] = [] + createResults(resultsFile: string, isDfa: boolean): Results { + this.createResultsCallHistory.push({ resultsFile, isDfa }) + return this.createResultsReturnValue + } +} + +export class FakeResults implements Results { + getSev1ViolationCountReturnValue = 1 + getSev1ViolationCountCallCount = 0 + getSev1ViolationCount(): number { + this.getSev1ViolationCountCallCount++ + return this.getSev1ViolationCountReturnValue + } + + getSev2ViolationCountReturnValue = 2 + getSev2ViolationCountCallCount = 0 + getSev2ViolationCount(): number { + this.getSev2ViolationCountCallCount++ + return this.getSev2ViolationCountReturnValue + } + + getSev3ViolationCountReturnValue = 3 + getSev3ViolationCountCallCount = 0 + getSev3ViolationCount(): number { + this.getSev3ViolationCountCallCount++ + return this.getSev3ViolationCountReturnValue + } + + getTotalViolationCountReturnValue = 6 + getTotalViolationCountCallCount = 0 + getTotalViolationCount(): number { + this.getTotalViolationCountCallCount++ + return this.getTotalViolationCountReturnValue + } + + getViolationsSortedBySeverityReturnValue: Violation[] = [] + getViolationsSortedBySeverityCallCount = 0 + getViolationsSortedBySeverity(): Violation[] { + this.getViolationsSortedBySeverityCallCount++ + return this.getViolationsSortedBySeverityReturnValue + } +} + +export class FakeViolationLocation implements ViolationLocation { + compareToReturnValue = 0 + compareToCallHistory: { other: ViolationLocation }[] = [] + compareTo(other: ViolationLocation): number { + this.compareToCallHistory.push({ other }) + return this.compareToReturnValue + } +} diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index c4fff0f..51abc8b 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,19 +1,21 @@ import * as main from '../src/main' -import { FakeCommandExecutor, FakeDependencies } from './fakes' +import { FakeCommandExecutor, FakeDependencies, FakeResultsFactory } from './fakes' import { Inputs } from '../src/types' import { INTERNAL_OUTFILE, MESSAGE_FCNS, MESSAGES, MIN_SCANNER_VERSION_REQUIRED } from '../src/constants' describe('main run Tests', () => { let dependencies: FakeDependencies let commandExecutor: FakeCommandExecutor + let resultsFactory: FakeResultsFactory beforeEach(async () => { dependencies = new FakeDependencies() commandExecutor = new FakeCommandExecutor() + resultsFactory = new FakeResultsFactory() }) it('Test default values', async () => { - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.isSalesforceCliInstalledCallCount).toEqual(1) @@ -27,14 +29,46 @@ describe('main run Tests', () => { expect(dependencies.uploadArtifactCallHistory).toHaveLength(1) expect(dependencies.uploadArtifactCallHistory).toContainEqual({ artifactName: 'code-analyzer-results', - artifactFiles: ['SalesforceCodeAnalyzerResults.json'] + artifactFiles: [INTERNAL_OUTFILE] }) - expect(dependencies.setOutputCallHistory).toHaveLength(1) + expect(resultsFactory.createResultsCallHistory).toHaveLength(1) + expect(resultsFactory.createResultsCallHistory).toContainEqual({ + resultsFile: INTERNAL_OUTFILE, + isDfa: false + }) + + expect(dependencies.setOutputCallHistory).toHaveLength(5) expect(dependencies.setOutputCallHistory).toContainEqual({ name: 'exit-code', value: '0' }) + expect(dependencies.setOutputCallHistory).toContainEqual({ + name: 'num-violations', + value: '6' + }) + expect(dependencies.setOutputCallHistory).toContainEqual({ + name: 'num-sev1-violations', + value: '1' + }) + expect(dependencies.setOutputCallHistory).toContainEqual({ + name: 'num-sev2-violations', + value: '2' + }) + expect(dependencies.setOutputCallHistory).toContainEqual({ + name: 'num-sev3-violations', + value: '3' + }) + + expect(dependencies.infoCallHistory).toContainEqual({ + infoMessage: + 'outputs:\n' + + ' exit-code: 0\n' + + ' num-violations: 6\n' + + ' num-sev1-violations: 1\n' + + ' num-sev2-violations: 2\n' + + ' num-sev3-violations: 3' + }) expect(dependencies.failCallHistory).toHaveLength(0) }) @@ -45,7 +79,7 @@ describe('main run Tests', () => { runArgs: '-o myFile.html --normalize-severity -t ./src', resultsArtifactName: 'customArtifactName' } - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.isSalesforceCliInstalledCallCount).toEqual(1) @@ -62,7 +96,12 @@ describe('main run Tests', () => { artifactFiles: ['myFile.html'] }) - expect(dependencies.setOutputCallHistory).toHaveLength(1) + expect(resultsFactory.createResultsCallHistory).toHaveLength(1) + expect(resultsFactory.createResultsCallHistory).toContainEqual({ + resultsFile: INTERNAL_OUTFILE, + isDfa: true + }) + expect(dependencies.setOutputCallHistory).toContainEqual({ name: 'exit-code', value: '0' @@ -73,7 +112,7 @@ describe('main run Tests', () => { it('Test nonzero exit code from command call', async () => { commandExecutor.runCodeAnalyzerReturnValue = 987 - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(dependencies.setOutputCallHistory).toContainEqual({ name: 'exit-code', @@ -90,7 +129,7 @@ describe('main run Tests', () => { } } dependencies = new ThrowingDependencies() - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.runCodeAnalyzerCallHistory).toHaveLength(0) expect(dependencies.uploadArtifactCallHistory).toHaveLength(0) @@ -102,7 +141,7 @@ describe('main run Tests', () => { it('Test missing --normalize-severity from run arguments', async () => { dependencies.getInputsReturnValue.runArgs = '--outfile results.xml' - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.runCodeAnalyzerCallHistory).toHaveLength(0) expect(dependencies.uploadArtifactCallHistory).toHaveLength(0) @@ -114,7 +153,7 @@ describe('main run Tests', () => { it('Test when Salesforce CLI is not already installed and we install it successfully', async () => { commandExecutor.isSalesforceCliInstalledReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(dependencies.warnCallHistory).toHaveLength(1) expect(dependencies.warnCallHistory).toContainEqual({ @@ -128,7 +167,7 @@ describe('main run Tests', () => { it('Test when Salesforce CLI is not already installed and we fail to install it', async () => { commandExecutor.isSalesforceCliInstalledReturnValue = false commandExecutor.installSalesforceCliReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(dependencies.warnCallHistory).toHaveLength(1) expect(dependencies.warnCallHistory).toContainEqual({ @@ -142,7 +181,7 @@ describe('main run Tests', () => { it('Test when sfdx-scanner plugin is not already installed and we install it successfully', async () => { commandExecutor.isMinimumScannerPluginInstalledReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(dependencies.warnCallHistory).toHaveLength(1) expect(dependencies.warnCallHistory).toContainEqual({ @@ -159,7 +198,7 @@ describe('main run Tests', () => { it('Test when sfdx-scanner plugin is not already installed and we fail to install it', async () => { commandExecutor.isMinimumScannerPluginInstalledReturnValue = false commandExecutor.installScannerPluginReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(dependencies.warnCallHistory).toHaveLength(1) expect(dependencies.warnCallHistory).toContainEqual({ @@ -176,7 +215,7 @@ describe('main run Tests', () => { it('Test when the internal outfile file does not exist after run then we fail', async () => { dependencies.fileExistsReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.runCodeAnalyzerCallHistory).toHaveLength(1) expect(dependencies.failCallHistory).toHaveLength(1) @@ -192,7 +231,7 @@ describe('main run Tests', () => { resultsArtifactName: 'customArtifactName' } dependencies.fileExistsReturnValue = false - await main.run(dependencies, commandExecutor) + await main.run(dependencies, commandExecutor, resultsFactory) expect(commandExecutor.runCodeAnalyzerCallHistory).toHaveLength(1) expect(dependencies.failCallHistory).toHaveLength(1) diff --git a/__tests__/results.test.ts b/__tests__/results.test.ts new file mode 100644 index 0000000..0ba95ce --- /dev/null +++ b/__tests__/results.test.ts @@ -0,0 +1,335 @@ +import { + Results, + ResultsFactory, + RunDfaViolationLocation, + RuntimeResultsFactory, + RuntimeViolation, + RunViolationLocation, + Violation, + ViolationLocation +} from '../src/results' +import * as path from 'path' +import { FakeViolationLocation } from './fakes' + +describe('RuntimeResultsFactory Tests', () => { + const resultsFactory: ResultsFactory = new RuntimeResultsFactory() + + it('Check that createResults correctly constructs run results', () => { + const results: Results = resultsFactory.createResults( + path.join('.', '__tests__', 'data', 'sampleRunResults.json'), + false + ) + expect(results.getTotalViolationCount()).toEqual(131) + expect(results.getSev1ViolationCount()).toEqual(55) + expect(results.getSev2ViolationCount()).toEqual(0) + expect(results.getSev3ViolationCount()).toEqual(76) + + const violations: Violation[] = results.getViolationsSortedBySeverity() + expect(violations).toHaveLength(131) + + // Check sorted by severity + for (let i = 0; i < 55; i++) { + expect(violations[i].getSeverity()).toEqual(1) + } + for (let i = 55; i < violations.length; i++) { + expect(violations[i].getSeverity()).toEqual(3) + } + + // Check one of the violations as a sanity check + expect(violations[5].getRuleEngine()).toEqual('eslint') + expect(violations[5].getRuleName()).toEqual('no-unused-vars') + expect(violations[5].getRuleUrl()).toEqual('https://eslint.org/docs/latest/rules/no-unused-vars') + expect(violations[5].getMessage()).toEqual("'helper' is defined but never used.") + expect(violations[5].getLocation().toString()).toEqual( + '/Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/aura/CSPattr/CSPattrController.js:2:40' + ) + }) + + it('Check that createResults correctly constructs run dfa results', () => { + const results: Results = resultsFactory.createResults( + path.join('.', '__tests__', 'data', 'sampleRunDfaResults.json'), + true + ) + expect(results.getTotalViolationCount()).toEqual(22) + expect(results.getSev1ViolationCount()).toEqual(18) + expect(results.getSev2ViolationCount()).toEqual(0) + expect(results.getSev3ViolationCount()).toEqual(4) + + const violations: Violation[] = results.getViolationsSortedBySeverity() + + // Check sorted by severity + expect(violations).toHaveLength(22) + for (let i = 0; i < 18; i++) { + expect(violations[i].getSeverity()).toEqual(1) + } + for (let i = 18; i < violations.length; i++) { + expect(violations[i].getSeverity()).toEqual(3) + } + + // Check one of the violations as a sanity check + expect(violations[1].getRuleEngine()).toEqual('sfge') + expect(violations[1].getRuleName()).toEqual('UseWithSharingOnDatabaseOperation') + expect(violations[1].getRuleUrl()).toEqual( + 'https://forcedotcom.github.io/sfdx-scanner/en/v3.x/salesforce-graph-engine/rules/#UseWithSharingOnDatabaseOperation' + ) + expect(violations[1].getMessage()).toEqual( + 'Database operation must be executed from a class that enforces sharing rules.' + ) + expect(violations[1].getLocation().toString()).toEqual( + 'Source: /Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/NameController.cls:5:26\n' + + 'Sink: /Users/runner/work/sample-sf-project/sample-sf-project/force-app/main/default/classes/UnsafeSOQL.cls:4' + ) + }) +}) + +describe('RuntimeViolation Tests', () => { + it('Test getters', () => { + const loc: FakeViolationLocation = new FakeViolationLocation() + const violation: Violation = new RuntimeViolation(2, 'engine', 'name', 'url', 'msg', loc) + expect(violation.getSeverity()).toEqual(2) + expect(violation.getRuleEngine()).toEqual('engine') + expect(violation.getRuleName()).toEqual('name') + expect(violation.getRuleUrl()).toEqual('url') + expect(violation.getMessage()).toEqual('msg') + expect(violation.getLocation()).toEqual(loc) + }) + + it('Test compareTo when severity is not the same', () => { + const loc1: FakeViolationLocation = new FakeViolationLocation() + loc1.compareToReturnValue = -1 + const loc2: FakeViolationLocation = new FakeViolationLocation() + loc2.compareToReturnValue = 1 + const v1: Violation = new RuntimeViolation(2, 'engine1', 'name1', 'url1', 'msg1', loc1) + const v2: Violation = new RuntimeViolation(1, 'engine2', 'name2', 'url2', 'msg2', loc2) + + expect(v1.compareTo(v2)).toEqual(1) + expect(v2.compareTo(v1)).toEqual(-1) + }) + + it('Test compareTo when severity the same but location is different', () => { + const loc1: FakeViolationLocation = new FakeViolationLocation() + loc1.compareToReturnValue = 1 + const loc2: FakeViolationLocation = new FakeViolationLocation() + loc2.compareToReturnValue = -1 + const v1: Violation = new RuntimeViolation(2, 'engine1', 'name1', 'url1', 'msg1', loc1) + const v2: Violation = new RuntimeViolation(2, 'engine2', 'name2', 'url2', 'msg2', loc2) + + expect(v1.compareTo(v2)).toEqual(1) + expect(v2.compareTo(v1)).toEqual(-1) + }) + + it('Test compareTo when severity and location are the same but engine is different', () => { + const loc1: FakeViolationLocation = new FakeViolationLocation() + const loc2: FakeViolationLocation = new FakeViolationLocation() + const v1: Violation = new RuntimeViolation(2, 'engineB', 'name1', 'url1', 'msg1', loc1) + const v2: Violation = new RuntimeViolation(2, 'engineA', 'name2', 'url2', 'msg2', loc2) + + expect(v1.compareTo(v2)).toEqual(1) + expect(v2.compareTo(v1)).toEqual(-1) + }) + + it('Test compareTo when severity, location, and engine are the same but rule name is different', () => { + const loc1: FakeViolationLocation = new FakeViolationLocation() + const loc2: FakeViolationLocation = new FakeViolationLocation() + const v1: Violation = new RuntimeViolation(2, 'engine', 'name1', 'url1', 'msg1', loc1) + const v2: Violation = new RuntimeViolation(2, 'engine', 'name2', 'url2', 'msg2', loc2) + + expect(v1.compareTo(v2)).toEqual(-1) + expect(v2.compareTo(v1)).toEqual(1) + }) + + it('Test compareTo when severity, location, engine, and rule name are the same', () => { + const loc1: FakeViolationLocation = new FakeViolationLocation() + const loc2: FakeViolationLocation = new FakeViolationLocation() + const v1: Violation = new RuntimeViolation(2, 'engine', 'name', 'url1', 'msg1', loc1) + const v2: Violation = new RuntimeViolation(2, 'engine', 'name', 'url2', 'msg2', loc2) + + expect(v1.compareTo(v2)).toEqual(0) + expect(v2.compareTo(v1)).toEqual(0) + }) +}) + +describe('RunViolationLocation Tests', () => { + it('Test toString value', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunViolationLocation('/some/file.apex', 12, 34) + expect(loc.toString()).toEqual('/some/file.apex:12:34') + }) + + it('Test toString with undefined line', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunViolationLocation('/some/file.apex', undefined, undefined) + expect(loc.toString()).toEqual('/some/file.apex') + }) + + it('Test toString with defined line but undefined column', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunViolationLocation('/some/file.apex', 12, undefined) + expect(loc.toString()).toEqual('/some/file.apex:12') + }) + + it('Test compareTo when file names are different', () => { + const loc1: ViolationLocation = new RunViolationLocation('fileB', 12, 34) + const loc2: ViolationLocation = new RunViolationLocation('fileA', 56, 78) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + }) + + it('Test compareTo when file names are same but lines are different', () => { + const loc1: ViolationLocation = new RunViolationLocation('file', 12, 99) + const loc2: ViolationLocation = new RunViolationLocation('file', 56, 11) + const loc3: ViolationLocation = new RunViolationLocation('file', undefined, 11) + expect(loc1.compareTo(loc2)).toEqual(-1) + expect(loc2.compareTo(loc1)).toEqual(1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when file names and line are same but columns are different', () => { + const loc1: ViolationLocation = new RunViolationLocation('file', 12, 99) + const loc2: ViolationLocation = new RunViolationLocation('file', 12, 11) + const loc3: ViolationLocation = new RunViolationLocation('file', 12, undefined) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when file names, lines, and columns are the same', () => { + const loc1: ViolationLocation = new RunViolationLocation('file', 12, 34) + const loc2: ViolationLocation = new RunViolationLocation('file', 12, 34) + const loc3: ViolationLocation = new RunViolationLocation('file', undefined, undefined) + expect(loc1.compareTo(loc2)).toEqual(0) + expect(loc2.compareTo(loc1)).toEqual(0) + expect(loc3.compareTo(loc3)).toEqual(0) + }) + + it('Test RunViolationLocation always comes before another ViolationLocation', () => { + // This will never happen in production, but best to cover this case for code coverage purposes + const loc1: ViolationLocation = new RunViolationLocation('file', 12, 34) + const loc2: ViolationLocation = new FakeViolationLocation() + expect(loc1.compareTo(loc2)).toEqual(-1) + }) +}) + +describe('RunDfaViolationLocation Tests', () => { + it('Test toString value', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunDfaViolationLocation( + '/some/sourceFile.apex', + 12, + 34, + '/some/sinkFile.apex', + 56, + 78 + ) + expect(loc.toString()).toEqual('Source: /some/sourceFile.apex:12:34\nSink: /some/sinkFile.apex:56:78') + }) + + it('Test toString value with undefined source lines and columns', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunDfaViolationLocation( + '/some/sourceFile.apex', + undefined, + undefined, + '/some/sinkFile.apex', + 12, + 13 + ) + expect(loc.toString()).toEqual('Source: /some/sourceFile.apex\nSink: /some/sinkFile.apex:12:13') + }) + + it('Test toString value with undefined sink lines and columns', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunDfaViolationLocation( + '/some/sourceFile.apex', + 99, + 22, + '/some/sinkFile.apex', + undefined, + undefined + ) + expect(loc.toString()).toEqual('Source: /some/sourceFile.apex:99:22\nSink: /some/sinkFile.apex') + }) + + it('Test toString value with defined lines but undefined columns', () => { + /* fileName: string, line: number, column: number, endLine: number, endColumn: number */ + const loc: ViolationLocation = new RunDfaViolationLocation( + '/some/sourceFile.apex', + 443, + undefined, + '/some/sinkFile.apex', + 331, + undefined + ) + expect(loc.toString()).toEqual('Source: /some/sourceFile.apex:443\nSink: /some/sinkFile.apex:331') + }) + + it('Test compareTo when source file names are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceB', 12, 34, 'sinkA', 12, 34) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 78, 'sinkB', 56, 78) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + }) + + it('Test compareTo when source file names are same but source lines are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 34) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 12, 78, 'sinkB', 56, 78) + const loc3: ViolationLocation = new RunDfaViolationLocation('sourceA', undefined, 78, 'sinkB', 56, 78) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when source file names line are same but source columns are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 34) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 78, 'sinkB', 56, 78) + const loc3: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, undefined, 'sinkB', 56, 78) + expect(loc1.compareTo(loc2)).toEqual(-1) + expect(loc2.compareTo(loc1)).toEqual(1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when sources are the same but sink file names are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkB', 12, 34) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 56, 78) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + }) + + it('Test compareTo when sources and sink file names are same but sink lines are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 78) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 56, 34) + const loc3: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', undefined, 34) + expect(loc1.compareTo(loc2)).toEqual(-1) + expect(loc2.compareTo(loc1)).toEqual(1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when sources and sink file names and lines are same but sink columns are different', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 78) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 34) + const loc3: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, undefined) + expect(loc1.compareTo(loc2)).toEqual(1) + expect(loc2.compareTo(loc1)).toEqual(-1) + expect(loc2.compareTo(loc3)).toEqual(-1) + expect(loc3.compareTo(loc2)).toEqual(1) + }) + + it('Test compareTo when sources and sinks are the same', () => { + const loc1: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 78) + const loc2: ViolationLocation = new RunDfaViolationLocation('sourceA', 56, 34, 'sinkA', 12, 78) + expect(loc1.compareTo(loc2)).toEqual(0) + expect(loc2.compareTo(loc1)).toEqual(0) + }) + + it('Test RunDfaViolationLocation always comes after another ViolationLocation', () => { + // This will never happen in production, but best to cover this case for code coverage purposes + const loc1: ViolationLocation = new RunDfaViolationLocation('source', 12, 34, 'sink', 56, 78) + const loc2: ViolationLocation = new FakeViolationLocation() + expect(loc1.compareTo(loc2)).toEqual(1) + }) +}) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index 5ab8c83..842be5d 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -1,5 +1,6 @@ -import { extractOutfileFromRunArguments, mergeWithProcessEnvVars } from '../src/utils' +import { extractOutfileFromRunArguments, mergeWithProcessEnvVars, toRelativeFile } from '../src/utils' import { EnvironmentVariables } from '../src/types' +import * as path from 'path' describe('Tests for mergeWithProcessEnvVars', () => { it('Test no new fields', async () => { @@ -76,3 +77,13 @@ describe('Tests for extractOutfileFromRunArguments', () => { expect(outfile1).toEqual('') }) }) + +describe('Tests for toRelativeFile', () => { + it('Relative stays relative', () => { + expect(toRelativeFile('some/file.json')).toEqual('some/file.json') + }) + + it('Absolute converts to relative', () => { + expect(toRelativeFile(path.resolve('.', 'action.yml'))).toEqual('action.yml') + }) +}) diff --git a/dist/index.js b/dist/index.js index 9d60484..0b2df15 100644 --- a/dist/index.js +++ b/dist/index.js @@ -103024,7 +103024,7 @@ const constants_1 = __nccwpck_require__(9042); * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ -async function run(dependencies, commandExecutor) { +async function run(dependencies, commandExecutor, resultsFactory) { try { dependencies.startGroup(constants_1.MESSAGES.STEP_LABELS.PREPARING_ENVIRONMENT); const inputs = dependencies.getInputs(); @@ -103044,7 +103044,18 @@ async function run(dependencies, commandExecutor) { dependencies.endGroup(); dependencies.startGroup(constants_1.MESSAGES.STEP_LABELS.ANALYZING_RESULTS); assertFileExists(dependencies, constants_1.INTERNAL_OUTFILE); - // TODO: Process the internal outfile and set the remaining output variables + const isDfa = inputs.runCommand === 'run dfa'; + const results = resultsFactory.createResults(constants_1.INTERNAL_OUTFILE, isDfa); + dependencies.setOutput('num-violations', results.getTotalViolationCount().toString()); + dependencies.setOutput('num-sev1-violations', results.getSev1ViolationCount().toString()); + dependencies.setOutput('num-sev2-violations', results.getSev2ViolationCount().toString()); + dependencies.setOutput('num-sev3-violations', results.getSev3ViolationCount().toString()); + dependencies.info(`outputs:\n` + + ` exit-code: ${codeAnalyzerExitCode}\n` + + ` num-violations: ${results.getTotalViolationCount()}\n` + + ` num-sev1-violations: ${results.getSev1ViolationCount()}\n` + + ` num-sev2-violations: ${results.getSev2ViolationCount()}\n` + + ` num-sev3-violations: ${results.getSev3ViolationCount()}`); dependencies.endGroup(); dependencies.startGroup(constants_1.MESSAGES.STEP_LABELS.CREATING_SUMMARY); // TODO: set the summary @@ -103085,15 +103096,304 @@ function assertFileExists(dependencies, file) { } +/***/ }), + +/***/ 2844: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RunDfaViolationLocation = exports.RunViolationLocation = exports.RuntimeViolation = exports.RuntimeResults = exports.RuntimeResultsFactory = void 0; +const fs = __importStar(__nccwpck_require__(7147)); +const utils_1 = __nccwpck_require__(1314); +class RuntimeResultsFactory { + createResults(resultsFile, isDfa) { + const jsonStr = fs.readFileSync(resultsFile, { encoding: 'utf8' }); + const resultArray = JSON.parse(jsonStr); + const violations = []; + for (const resultObj of resultArray) { + for (const violationObj of resultObj['violations']) { + let violationLocation; + if (isDfa) { + violationLocation = new RunDfaViolationLocation((0, utils_1.toRelativeFile)(resultObj['fileName']), violationObj['sourceLine'], violationObj['sourceColumn'], (0, utils_1.toRelativeFile)(violationObj['sinkFileName']), violationObj['sinkLine'], violationObj['sinkObj']); + } + else { + violationLocation = new RunViolationLocation((0, utils_1.toRelativeFile)(resultObj['fileName']), violationObj['line'], violationObj['column']); + } + violations.push(new RuntimeViolation(violationObj['normalizedSeverity'], resultObj['engine'], violationObj['ruleName'], violationObj['url'], violationObj['message'], violationLocation)); + } + } + return new RuntimeResults(violations); + } +} +exports.RuntimeResultsFactory = RuntimeResultsFactory; +class RuntimeResults { + violations; + sorted = false; + constructor(violations) { + this.violations = violations; + } + getTotalViolationCount() { + return this.violations.length; + } + getSev1ViolationCount() { + return this.violations.filter(v => v.getSeverity() === 1).length; + } + getSev2ViolationCount() { + return this.violations.filter(v => v.getSeverity() === 2).length; + } + getSev3ViolationCount() { + return this.violations.filter(v => v.getSeverity() === 3).length; + } + getViolationsSortedBySeverity() { + if (!this.sorted) { + this.violations.sort((v1, v2) => v1.compareTo(v2)); + this.sorted = true; + } + return this.violations; + } +} +exports.RuntimeResults = RuntimeResults; +class RuntimeViolation { + severity; + ruleEngine; + ruleName; + ruleUrl; + message; + location; + constructor(severity, ruleEngine, ruleName, ruleUrl, message, location) { + this.severity = severity; + this.ruleEngine = ruleEngine; + this.ruleName = ruleName; + this.ruleUrl = ruleUrl; + this.message = message; + this.location = location; + } + getSeverity() { + return this.severity; + } + getLocation() { + return this.location; + } + getRuleEngine() { + return this.ruleEngine; + } + getRuleName() { + return this.ruleName; + } + getRuleUrl() { + return this.ruleUrl; + } + getMessage() { + return this.message; + } + compareTo(other) { + if (this.getSeverity() !== other.getSeverity()) { + return this.getSeverity() - other.getSeverity(); + } + const locationCompare = this.getLocation().compareTo(other.getLocation()); + if (locationCompare !== 0) { + return locationCompare; + } + if (this.getRuleEngine() !== other.getRuleEngine()) { + return this.getRuleEngine() < other.getRuleEngine() ? -1 : 1; + } + if (this.getRuleName() !== other.getRuleName()) { + return this.getRuleName() < other.getRuleName() ? -1 : 1; + } + return 0; + } +} +exports.RuntimeViolation = RuntimeViolation; +class RunViolationLocation { + fileName; + line; + column; + constructor(fileName, line, column) { + this.fileName = fileName; + this.line = line; + this.column = column; + } + toString() { + let locStr = this.fileName; + if (this.line !== undefined) { + locStr += `:${this.line}`; + if (this.column !== undefined) { + locStr += `:${this.column}`; + } + } + return locStr; + } + compareTo(other) { + if (!(other instanceof RunViolationLocation)) { + return -1; + } + if (this.fileName !== other.fileName) { + return this.fileName < other.fileName ? -1 : 1; + } + else if (this.line !== other.line) { + if (this.line === undefined) { + return 1; + } + else if (other.line === undefined) { + return -1; + } + return this.line < other.line ? -1 : 1; + } + else if (this.column !== other.column) { + if (this.column === undefined) { + return 1; + } + else if (other.column === undefined) { + return -1; + } + return this.column < other.column ? -1 : 1; + } + return 0; + } +} +exports.RunViolationLocation = RunViolationLocation; +class RunDfaViolationLocation { + sourceFileName; + sourceLine; + sourceColumn; + sinkFileName; + sinkLine; + sinkColumn; + constructor(sourceFileName, sourceLine, sourceColumn, sinkFileName, sinkLine, sinkColumn) { + this.sourceFileName = sourceFileName; + this.sourceLine = sourceLine; + this.sourceColumn = sourceColumn; + this.sinkFileName = sinkFileName; + this.sinkLine = sinkLine; + this.sinkColumn = sinkColumn; + } + toString() { + let locStr = `Source: ${this.sourceFileName}`; + if (this.sourceLine !== undefined) { + locStr += `:${this.sourceLine}`; + if (this.sourceColumn !== undefined) { + locStr += `:${this.sourceColumn}`; + } + } + locStr += `\nSink: ${this.sinkFileName}`; + if (this.sinkLine !== undefined) { + locStr += `:${this.sinkLine}`; + if (this.sinkColumn !== undefined) { + locStr += `:${this.sinkColumn}`; + } + } + return locStr; + } + compareTo(other) { + if (!(other instanceof RunDfaViolationLocation)) { + return 1; + } + if (this.sourceFileName !== other.sourceFileName) { + return this.sourceFileName < other.sourceFileName ? -1 : 1; + } + else if (this.sourceLine !== other.sourceLine) { + if (this.sourceLine === undefined) { + return 1; + } + else if (other.sourceLine === undefined) { + return -1; + } + return this.sourceLine < other.sourceLine ? -1 : 1; + } + else if (this.sourceColumn !== other.sourceColumn) { + if (this.sourceColumn === undefined) { + return 1; + } + else if (other.sourceColumn === undefined) { + return -1; + } + return this.sourceColumn < other.sourceColumn ? -1 : 1; + } + else if (this.sinkFileName !== other.sinkFileName) { + return this.sinkFileName < other.sinkFileName ? -1 : 1; + } + else if (this.sinkLine !== other.sinkLine) { + if (this.sinkLine === undefined) { + return 1; + } + else if (other.sinkLine === undefined) { + return -1; + } + return this.sinkLine < other.sinkLine ? -1 : 1; + } + else if (this.sinkColumn !== other.sinkColumn) { + if (this.sinkColumn === undefined) { + return 1; + } + else if (other.sinkColumn === undefined) { + return -1; + } + return this.sinkColumn < other.sinkColumn ? -1 : 1; + } + return 0; + } +} +exports.RunDfaViolationLocation = RunDfaViolationLocation; + + /***/ }), /***/ 1314: -/***/ ((__unused_webpack_module, exports) => { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.extractOutfileFromRunArguments = exports.mergeWithProcessEnvVars = void 0; +exports.toRelativeFile = exports.extractOutfileFromRunArguments = exports.mergeWithProcessEnvVars = void 0; +const path = __importStar(__nccwpck_require__(1017)); /** * Returns a copy of the current process environment variables with the supplied environment variables added */ @@ -103154,6 +103454,11 @@ function markSpacesBetweenQuotes(value, spaceMarker) { } return output; } +const pwd = path.resolve('.') + path.sep; +const toRelativeFile = (file) => { + return file.startsWith(pwd) ? file.substring(pwd.length) : file; +}; +exports.toRelativeFile = toRelativeFile; /***/ }), @@ -103414,13 +103719,15 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const main_1 = __nccwpck_require__(399); const dependencies_1 = __nccwpck_require__(7760); const commands_1 = __nccwpck_require__(6695); +const results_1 = __nccwpck_require__(2844); /** * The entrypoint for the action. */ const runtimeDependencies = new dependencies_1.RuntimeDependencies(); const commandExecutor = new commands_1.RuntimeCommandExecutor(runtimeDependencies); +const resultsFactory = new results_1.RuntimeResultsFactory(); // eslint-disable-next-line @typescript-eslint/no-floating-promises -(0, main_1.run)(runtimeDependencies, commandExecutor); +(0, main_1.run)(runtimeDependencies, commandExecutor, resultsFactory); })(); diff --git a/src/index.ts b/src/index.ts index f5fa8d6..4299a02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,13 @@ import { run } from './main' import { Dependencies, RuntimeDependencies } from './dependencies' import { CommandExecutor, RuntimeCommandExecutor } from './commands' +import { ResultsFactory, RuntimeResultsFactory } from './results' /** * The entrypoint for the action. */ const runtimeDependencies: Dependencies = new RuntimeDependencies() const commandExecutor: CommandExecutor = new RuntimeCommandExecutor(runtimeDependencies) +const resultsFactory: ResultsFactory = new RuntimeResultsFactory() // eslint-disable-next-line @typescript-eslint/no-floating-promises -run(runtimeDependencies, commandExecutor) +run(runtimeDependencies, commandExecutor, resultsFactory) diff --git a/src/main.ts b/src/main.ts index 6b26c57..d54f776 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,12 +3,17 @@ import { Dependencies } from './dependencies' import { Inputs } from './types' import { CommandExecutor } from './commands' import { INTERNAL_OUTFILE, MESSAGE_FCNS, MESSAGES, MIN_SCANNER_VERSION_REQUIRED } from './constants' +import { Results, ResultsFactory } from './results' /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ -export async function run(dependencies: Dependencies, commandExecutor: CommandExecutor): Promise { +export async function run( + dependencies: Dependencies, + commandExecutor: CommandExecutor, + resultsFactory: ResultsFactory +): Promise { try { dependencies.startGroup(MESSAGES.STEP_LABELS.PREPARING_ENVIRONMENT) const inputs: Inputs = dependencies.getInputs() @@ -35,7 +40,20 @@ export async function run(dependencies: Dependencies, commandExecutor: CommandEx dependencies.startGroup(MESSAGES.STEP_LABELS.ANALYZING_RESULTS) assertFileExists(dependencies, INTERNAL_OUTFILE) - // TODO: Process the internal outfile and set the remaining output variables + const isDfa = inputs.runCommand === 'run dfa' + const results: Results = resultsFactory.createResults(INTERNAL_OUTFILE, isDfa) + dependencies.setOutput('num-violations', results.getTotalViolationCount().toString()) + dependencies.setOutput('num-sev1-violations', results.getSev1ViolationCount().toString()) + dependencies.setOutput('num-sev2-violations', results.getSev2ViolationCount().toString()) + dependencies.setOutput('num-sev3-violations', results.getSev3ViolationCount().toString()) + dependencies.info( + `outputs:\n` + + ` exit-code: ${codeAnalyzerExitCode}\n` + + ` num-violations: ${results.getTotalViolationCount()}\n` + + ` num-sev1-violations: ${results.getSev1ViolationCount()}\n` + + ` num-sev2-violations: ${results.getSev2ViolationCount()}\n` + + ` num-sev3-violations: ${results.getSev3ViolationCount()}` + ) dependencies.endGroup() dependencies.startGroup(MESSAGES.STEP_LABELS.CREATING_SUMMARY) diff --git a/src/results.ts b/src/results.ts new file mode 100644 index 0000000..78bc9e7 --- /dev/null +++ b/src/results.ts @@ -0,0 +1,300 @@ +import * as fs from 'fs' +import { toRelativeFile } from './utils' + +export interface ResultsFactory { + createResults(resultsFile: string, isDfa: boolean): Results +} + +export interface Results { + getTotalViolationCount(): number + getSev1ViolationCount(): number + getSev2ViolationCount(): number + getSev3ViolationCount(): number + getViolationsSortedBySeverity(): Violation[] +} + +export interface Violation { + getSeverity(): number + getLocation(): ViolationLocation + getRuleEngine(): string + getRuleName(): string + getRuleUrl(): string | undefined + getMessage(): string + compareTo(other: Violation): number +} + +export interface ViolationLocation { + toString(): string + compareTo(other: ViolationLocation): number +} + +export class RuntimeResultsFactory implements ResultsFactory { + createResults(resultsFile: string, isDfa: boolean): Results { + const jsonStr: string = fs.readFileSync(resultsFile, { encoding: 'utf8' }) + const resultArray = JSON.parse(jsonStr) + + const violations: Violation[] = [] + for (const resultObj of resultArray) { + for (const violationObj of resultObj['violations']) { + let violationLocation: ViolationLocation + if (isDfa) { + violationLocation = new RunDfaViolationLocation( + toRelativeFile(resultObj['fileName']), + violationObj['sourceLine'], + violationObj['sourceColumn'], + toRelativeFile(violationObj['sinkFileName']), + violationObj['sinkLine'], + violationObj['sinkObj'] + ) + } else { + violationLocation = new RunViolationLocation( + toRelativeFile(resultObj['fileName']), + violationObj['line'], + violationObj['column'] + ) + } + + violations.push( + new RuntimeViolation( + violationObj['normalizedSeverity'], + resultObj['engine'], + violationObj['ruleName'], + violationObj['url'], + violationObj['message'], + violationLocation + ) + ) + } + } + return new RuntimeResults(violations) + } +} + +export class RuntimeResults implements Results { + private readonly violations: Violation[] + private sorted = false + + constructor(violations: Violation[]) { + this.violations = violations + } + + getTotalViolationCount(): number { + return this.violations.length + } + + getSev1ViolationCount(): number { + return this.violations.filter(v => v.getSeverity() === 1).length + } + + getSev2ViolationCount(): number { + return this.violations.filter(v => v.getSeverity() === 2).length + } + + getSev3ViolationCount(): number { + return this.violations.filter(v => v.getSeverity() === 3).length + } + + getViolationsSortedBySeverity(): Violation[] { + if (!this.sorted) { + this.violations.sort((v1, v2) => v1.compareTo(v2)) + this.sorted = true + } + return this.violations + } +} + +export class RuntimeViolation implements Violation { + private readonly severity: number + private readonly ruleEngine: string + private readonly ruleName: string + private readonly ruleUrl?: string + private readonly message: string + private readonly location: ViolationLocation + + constructor( + severity: number, + ruleEngine: string, + ruleName: string, + ruleUrl: string | undefined, + message: string, + location: ViolationLocation + ) { + this.severity = severity + this.ruleEngine = ruleEngine + this.ruleName = ruleName + this.ruleUrl = ruleUrl + this.message = message + this.location = location + } + + getSeverity(): number { + return this.severity + } + + getLocation(): ViolationLocation { + return this.location + } + + getRuleEngine(): string { + return this.ruleEngine + } + + getRuleName(): string { + return this.ruleName + } + + getRuleUrl(): string | undefined { + return this.ruleUrl + } + + getMessage(): string { + return this.message + } + + compareTo(other: Violation): number { + if (this.getSeverity() !== other.getSeverity()) { + return this.getSeverity() - other.getSeverity() + } + const locationCompare: number = this.getLocation().compareTo(other.getLocation()) + if (locationCompare !== 0) { + return locationCompare + } + if (this.getRuleEngine() !== other.getRuleEngine()) { + return this.getRuleEngine() < other.getRuleEngine() ? -1 : 1 + } + if (this.getRuleName() !== other.getRuleName()) { + return this.getRuleName() < other.getRuleName() ? -1 : 1 + } + return 0 + } +} + +export class RunViolationLocation implements ViolationLocation { + private readonly fileName: string + private readonly line: number | undefined + private readonly column: number | undefined + + constructor(fileName: string, line: number | undefined, column: number | undefined) { + this.fileName = fileName + this.line = line + this.column = column + } + + toString(): string { + let locStr = this.fileName + if (this.line !== undefined) { + locStr += `:${this.line}` + if (this.column !== undefined) { + locStr += `:${this.column}` + } + } + return locStr + } + + compareTo(other: ViolationLocation): number { + if (!(other instanceof RunViolationLocation)) { + return -1 + } + if (this.fileName !== other.fileName) { + return this.fileName < other.fileName ? -1 : 1 + } else if (this.line !== other.line) { + if (this.line === undefined) { + return 1 + } else if (other.line === undefined) { + return -1 + } + return this.line < other.line ? -1 : 1 + } else if (this.column !== other.column) { + if (this.column === undefined) { + return 1 + } else if (other.column === undefined) { + return -1 + } + return this.column < other.column ? -1 : 1 + } + return 0 + } +} + +export class RunDfaViolationLocation implements ViolationLocation { + private readonly sourceFileName: string + private readonly sourceLine: number | undefined + private readonly sourceColumn: number | undefined + private readonly sinkFileName: string + private readonly sinkLine: number | undefined + private readonly sinkColumn: number | undefined + + constructor( + sourceFileName: string, + sourceLine: number | undefined, + sourceColumn: number | undefined, + sinkFileName: string, + sinkLine: number | undefined, + sinkColumn: number | undefined + ) { + this.sourceFileName = sourceFileName + this.sourceLine = sourceLine + this.sourceColumn = sourceColumn + this.sinkFileName = sinkFileName + this.sinkLine = sinkLine + this.sinkColumn = sinkColumn + } + + toString(): string { + let locStr = `Source: ${this.sourceFileName}` + if (this.sourceLine !== undefined) { + locStr += `:${this.sourceLine}` + if (this.sourceColumn !== undefined) { + locStr += `:${this.sourceColumn}` + } + } + locStr += `\nSink: ${this.sinkFileName}` + if (this.sinkLine !== undefined) { + locStr += `:${this.sinkLine}` + if (this.sinkColumn !== undefined) { + locStr += `:${this.sinkColumn}` + } + } + return locStr + } + + compareTo(other: ViolationLocation): number { + if (!(other instanceof RunDfaViolationLocation)) { + return 1 + } + if (this.sourceFileName !== other.sourceFileName) { + return this.sourceFileName < other.sourceFileName ? -1 : 1 + } else if (this.sourceLine !== other.sourceLine) { + if (this.sourceLine === undefined) { + return 1 + } else if (other.sourceLine === undefined) { + return -1 + } + return this.sourceLine < other.sourceLine ? -1 : 1 + } else if (this.sourceColumn !== other.sourceColumn) { + if (this.sourceColumn === undefined) { + return 1 + } else if (other.sourceColumn === undefined) { + return -1 + } + return this.sourceColumn < other.sourceColumn ? -1 : 1 + } else if (this.sinkFileName !== other.sinkFileName) { + return this.sinkFileName < other.sinkFileName ? -1 : 1 + } else if (this.sinkLine !== other.sinkLine) { + if (this.sinkLine === undefined) { + return 1 + } else if (other.sinkLine === undefined) { + return -1 + } + return this.sinkLine < other.sinkLine ? -1 : 1 + } else if (this.sinkColumn !== other.sinkColumn) { + if (this.sinkColumn === undefined) { + return 1 + } else if (other.sinkColumn === undefined) { + return -1 + } + return this.sinkColumn < other.sinkColumn ? -1 : 1 + } + return 0 + } +} diff --git a/src/utils.ts b/src/utils.ts index 84fa4fd..5bcf843 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import { EnvironmentVariables } from './types' +import * as path from 'path' /** * Returns a copy of the current process environment variables with the supplied environment variables added @@ -54,3 +55,8 @@ function markSpacesBetweenQuotes(value: string, spaceMarker: string): string { } return output } + +const pwd: string = path.resolve('.') + path.sep +export const toRelativeFile = (file: string): string => { + return file.startsWith(pwd) ? file.substring(pwd.length) : file +}