@@ -52,6 +52,14 @@ const EXPECTED_NoTrailingWhitespace_RULE_DESCRIPTION: RuleDescription = {
5252 resourceUrls : [ ]
5353} ;
5454
55+ const EXPECTED_NoMixedIndentation_RULE_DESCRIPTION : RuleDescription = {
56+ name : "NoMixedIndentation" ,
57+ severityLevel : SeverityLevel . Moderate ,
58+ tags : [ "Recommended" , "CodeStyle" , "Apex" ] ,
59+ description : getMessage ( 'MixedIndentationRuleDescription' ) ,
60+ resourceUrls : [ ]
61+ } ;
62+
5563const EXPECTED_NoTodos_RULE_DESCRIPTION : RuleDescription = {
5664 name : "NoTodos" ,
5765 severityLevel : DEFAULT_SEVERITY_LEVEL ,
@@ -126,15 +134,16 @@ describe("Tests for RegexEngine's getName and describeRules methods", () => {
126134
127135 it ( 'Calling describeRules without workspace, returns all available rules' , async ( ) => {
128136 const rulesDescriptions : RuleDescription [ ] = await engine . describeRules ( createDescribeOptions ( ) ) ;
129- expect ( rulesDescriptions ) . toHaveLength ( 8 ) ;
137+ expect ( rulesDescriptions ) . toHaveLength ( 9 ) ;
130138 expect ( rulesDescriptions [ 0 ] ) . toMatchObject ( EXPECTED_NoTrailingWhitespace_RULE_DESCRIPTION ) ;
131- expect ( rulesDescriptions [ 1 ] ) . toMatchObject ( EXPECTED_AvoidTermsWithImplicitBias_RULE_DESCRIPTION )
132- expect ( rulesDescriptions [ 2 ] ) . toMatchObject ( EXPECTED_AvoidOldSalesforceApiVersions_RULE_DESCRIPTION )
133- expect ( rulesDescriptions [ 3 ] ) . toMatchObject ( EXPECTED_NoGetHeapSizeInLoop_RULE_DESCRIPTION )
134- expect ( rulesDescriptions [ 4 ] ) . toMatchObject ( EXPECTED_MinVersionForAbstractVirtualClassesWithPrivateMethod_RULE_DESCRIPTION )
135- expect ( rulesDescriptions [ 5 ] ) . toMatchObject ( EXPECTED_NoTodos_RULE_DESCRIPTION ) ;
136- expect ( rulesDescriptions [ 6 ] ) . toMatchObject ( EXPECTED_NoHellos_RULE_DESCRIPTION ) ;
137- expect ( rulesDescriptions [ 7 ] ) . toMatchObject ( EXPECTED_NoTalkingAboutFightClub_RULE_DESCRIPTION ) ;
139+ expect ( rulesDescriptions [ 1 ] ) . toMatchObject ( EXPECTED_NoMixedIndentation_RULE_DESCRIPTION ) ;
140+ expect ( rulesDescriptions [ 2 ] ) . toMatchObject ( EXPECTED_AvoidTermsWithImplicitBias_RULE_DESCRIPTION ) ;
141+ expect ( rulesDescriptions [ 3 ] ) . toMatchObject ( EXPECTED_AvoidOldSalesforceApiVersions_RULE_DESCRIPTION ) ;
142+ expect ( rulesDescriptions [ 4 ] ) . toMatchObject ( EXPECTED_NoGetHeapSizeInLoop_RULE_DESCRIPTION ) ;
143+ expect ( rulesDescriptions [ 5 ] ) . toMatchObject ( EXPECTED_MinVersionForAbstractVirtualClassesWithPrivateMethod_RULE_DESCRIPTION ) ;
144+ expect ( rulesDescriptions [ 6 ] ) . toMatchObject ( EXPECTED_NoTodos_RULE_DESCRIPTION ) ;
145+ expect ( rulesDescriptions [ 7 ] ) . toMatchObject ( EXPECTED_NoHellos_RULE_DESCRIPTION ) ;
146+ expect ( rulesDescriptions [ 8 ] ) . toMatchObject ( EXPECTED_NoTalkingAboutFightClub_RULE_DESCRIPTION ) ;
138147 } ) ;
139148
140149 it ( "When workspace targeting zero applicable files, then describeRules returns no rules" , async ( ) => {
@@ -157,14 +166,15 @@ describe("Tests for RegexEngine's getName and describeRules methods", () => {
157166 it ( "When workspace contains files are applicable to all available rules, then describeRules returns all rules" , async ( ) => {
158167 const rulesDescriptions : RuleDescription [ ] = await engine . describeRules ( createDescribeOptions (
159168 new Workspace ( 'id' , [ path . resolve ( __dirname , 'test-data' , 'sampleWorkspace' ) ] ) ) ) ;
160- expect ( rulesDescriptions ) . toHaveLength ( 7 ) ;
169+ expect ( rulesDescriptions ) . toHaveLength ( 8 ) ;
161170 expect ( rulesDescriptions [ 0 ] ) . toMatchObject ( EXPECTED_NoTrailingWhitespace_RULE_DESCRIPTION ) ;
162- expect ( rulesDescriptions [ 1 ] ) . toMatchObject ( EXPECTED_AvoidTermsWithImplicitBias_RULE_DESCRIPTION ) ;
163- expect ( rulesDescriptions [ 2 ] ) . toMatchObject ( EXPECTED_AvoidOldSalesforceApiVersions_RULE_DESCRIPTION ) ;
164- expect ( rulesDescriptions [ 3 ] ) . toMatchObject ( EXPECTED_NoGetHeapSizeInLoop_RULE_DESCRIPTION ) ;
165- expect ( rulesDescriptions [ 4 ] ) . toMatchObject ( EXPECTED_MinVersionForAbstractVirtualClassesWithPrivateMethod_RULE_DESCRIPTION ) ;
166- expect ( rulesDescriptions [ 5 ] ) . toMatchObject ( EXPECTED_NoTodos_RULE_DESCRIPTION ) ;
167- expect ( rulesDescriptions [ 6 ] ) . toMatchObject ( EXPECTED_NoHellos_RULE_DESCRIPTION ) ;
171+ expect ( rulesDescriptions [ 1 ] ) . toMatchObject ( EXPECTED_NoMixedIndentation_RULE_DESCRIPTION ) ;
172+ expect ( rulesDescriptions [ 2 ] ) . toMatchObject ( EXPECTED_AvoidTermsWithImplicitBias_RULE_DESCRIPTION ) ;
173+ expect ( rulesDescriptions [ 3 ] ) . toMatchObject ( EXPECTED_AvoidOldSalesforceApiVersions_RULE_DESCRIPTION ) ;
174+ expect ( rulesDescriptions [ 4 ] ) . toMatchObject ( EXPECTED_NoGetHeapSizeInLoop_RULE_DESCRIPTION ) ;
175+ expect ( rulesDescriptions [ 5 ] ) . toMatchObject ( EXPECTED_MinVersionForAbstractVirtualClassesWithPrivateMethod_RULE_DESCRIPTION ) ;
176+ expect ( rulesDescriptions [ 6 ] ) . toMatchObject ( EXPECTED_NoTodos_RULE_DESCRIPTION ) ;
177+ expect ( rulesDescriptions [ 7 ] ) . toMatchObject ( EXPECTED_NoHellos_RULE_DESCRIPTION ) ;
168178 } ) ;
169179} ) ;
170180
@@ -959,6 +969,66 @@ describe('Tests for runRules', () => {
959969 expect ( combinedRunViolations ) . toContainEqual ( individualRunViolation ) ;
960970 }
961971 } ) ;
972+
973+ it ( "NoMixedIndentation rule should detect tab followed by spaces in indentation" , async ( ) => {
974+ const runOptions : RunOptions = createRunOptions (
975+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "mixedIndentation_TabThenSpaces.cls" ) ] ) ) ;
976+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
977+
978+ expect ( runResults . violations . length ) . toBeGreaterThan ( 0 ) ;
979+ expect ( runResults . violations [ 0 ] . ruleName ) . toBe ( "NoMixedIndentation" ) ;
980+ expect ( runResults . violations [ 0 ] . message ) . toBe ( getMessage ( 'MixedIndentationRuleMessage' ) ) ;
981+ } ) ;
982+
983+ it ( "NoMixedIndentation rule should detect spaces followed by tab in indentation" , async ( ) => {
984+ const runOptions : RunOptions = createRunOptions (
985+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "mixedIndentation_SpacesThenTab.cls" ) ] ) ) ;
986+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
987+
988+ expect ( runResults . violations . length ) . toBeGreaterThan ( 0 ) ;
989+ expect ( runResults . violations [ 0 ] . ruleName ) . toBe ( "NoMixedIndentation" ) ;
990+ expect ( runResults . violations [ 0 ] . message ) . toBe ( getMessage ( 'MixedIndentationRuleMessage' ) ) ;
991+ } ) ;
992+
993+ it ( "NoMixedIndentation rule should NOT flag lines with only spaces" , async ( ) => {
994+ const runOptions : RunOptions = createRunOptions (
995+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "validIndentation_OnlySpaces.cls" ) ] ) ) ;
996+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
997+
998+ expect ( runResults . violations ) . toHaveLength ( 0 ) ;
999+ } ) ;
1000+
1001+ it ( "NoMixedIndentation rule should NOT flag lines with only tabs" , async ( ) => {
1002+ const runOptions : RunOptions = createRunOptions (
1003+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "validIndentation_OnlyTabs.cls" ) ] ) ) ;
1004+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
1005+
1006+ expect ( runResults . violations ) . toHaveLength ( 0 ) ;
1007+ } ) ;
1008+
1009+ it ( "NoMixedIndentation rule should NOT create false flags for valid patterns" , async ( ) => {
1010+ const runOptions : RunOptions = createRunOptions (
1011+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "validIndentation_NoFalseFlags.cls" ) ] ) ) ;
1012+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
1013+
1014+ // Should have 0 violations - all patterns are valid
1015+ expect ( runResults . violations ) . toHaveLength ( 0 ) ;
1016+ } ) ;
1017+
1018+ it ( "NoMixedIndentation rule should detect edge case violations" , async ( ) => {
1019+ const runOptions : RunOptions = createRunOptions (
1020+ new Workspace ( 'id' , [ path . resolve ( __dirname , "test-data" , "apexClassMixedIndentation" , "mixedIndentation_EdgeCases.cls" ) ] ) ) ;
1021+ const runResults : EngineRunResults = await engine . runRules ( [ "NoMixedIndentation" ] , runOptions ) ;
1022+
1023+ // Should detect multiple violations
1024+ expect ( runResults . violations . length ) . toBeGreaterThan ( 0 ) ;
1025+
1026+ // All violations should be NoMixedIndentation
1027+ for ( const violation of runResults . violations ) {
1028+ expect ( violation . ruleName ) . toBe ( "NoMixedIndentation" ) ;
1029+ expect ( violation . message ) . toBe ( getMessage ( 'MixedIndentationRuleMessage' ) ) ;
1030+ }
1031+ } ) ;
9621032} ) ;
9631033
9641034describe ( 'Tests for getEngineVersion' , ( ) => {
0 commit comments