@@ -34,6 +34,7 @@ const workspaceWithNoCustomConfig: string = path.join(testDataFolder, 'workspace
3434const workspaceThatHasCustomConfigModifyingExistingRules : string = path . join ( testDataFolder , 'workspaceWithFlatConfigCjs' ) ;
3535const workspaceThatHasCustomConfigWithNewRules : string = path . join ( testDataFolder , 'workspaceWithFlatConfigWithNewRules' ) ;
3636const workspaceWithReactFiles : string = path . join ( testDataFolder , 'workspaceWithReactFiles' ) ;
37+ const workspaceWithFixableViolations : string = path . join ( testDataFolder , 'workspace_FixableViolations' ) ;
3738
3839let original_working_directory : string ;
3940beforeAll ( ( ) => {
@@ -1009,6 +1010,180 @@ describe('Tests for emitting events', () => {
10091010 } ) ;
10101011} ) ;
10111012
1013+ describe ( 'Tests for Fixable tag on rule descriptions' , ( ) => {
1014+ it ( 'When a rule has fixable metadata, the Fixable tag is included in its description' , async ( ) => {
1015+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1016+ const describeOptions = createDescribeOptions ( new Workspace ( 'id' , [ workspaceWithNoCustomConfig ] ) ) ;
1017+ const ruleDescriptions : RuleDescription [ ] = await engine . describeRules ( describeOptions ) ;
1018+
1019+ const preferConstRule = ruleDescriptions . find ( r => r . name === 'prefer-const' ) ;
1020+ expect ( preferConstRule ) . toBeDefined ( ) ;
1021+ expect ( preferConstRule ! . tags ) . toContain ( 'Fixable' ) ;
1022+ } ) ;
1023+
1024+ it ( 'When a rule does not have fixable metadata, the Fixable tag is not present' , async ( ) => {
1025+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1026+ const describeOptions = createDescribeOptions ( new Workspace ( 'id' , [ workspaceWithNoCustomConfig ] ) ) ;
1027+ const ruleDescriptions : RuleDescription [ ] = await engine . describeRules ( describeOptions ) ;
1028+
1029+ const noInvalidRegexpRule = ruleDescriptions . find ( r => r . name === 'no-invalid-regexp' ) ;
1030+ expect ( noInvalidRegexpRule ) . toBeDefined ( ) ;
1031+ expect ( noInvalidRegexpRule ! . tags ) . not . toContain ( 'Fixable' ) ;
1032+ } ) ;
1033+ } ) ;
1034+
1035+ describe ( 'Tests for fixes and suggestions in runRules' , ( ) => {
1036+ const fixableFile : string = path . join ( workspaceWithFixableViolations , 'fixable.js' ) ;
1037+
1038+ it ( 'When includeFixes is true, violations from fixable rules include fixes' , async ( ) => {
1039+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1040+ const runOptions : RunOptions = {
1041+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1042+ includeFixes : true
1043+ } ;
1044+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' ] , runOptions ) ;
1045+
1046+ expect ( results . violations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1047+ const violationWithFix = results . violations . find ( v => v . fixes && v . fixes . length > 0 ) ;
1048+ expect ( violationWithFix ) . toBeDefined ( ) ;
1049+
1050+ const fix = violationWithFix ! . fixes ! [ 0 ] ;
1051+ expect ( fix . fixedCode ) . toBeDefined ( ) ;
1052+ expect ( fix . location . file ) . toEqual ( fixableFile ) ;
1053+ expect ( fix . location . startLine ) . toBeGreaterThanOrEqual ( 1 ) ;
1054+ expect ( fix . location . startColumn ) . toBeGreaterThanOrEqual ( 1 ) ;
1055+ expect ( fix . location . endLine ) . toBeGreaterThanOrEqual ( fix . location . startLine ) ;
1056+ } ) ;
1057+
1058+ it ( 'When includeFixes is false, violations do not include fixes even for fixable rules' , async ( ) => {
1059+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1060+ const runOptions : RunOptions = {
1061+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1062+ includeFixes : false
1063+ } ;
1064+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' ] , runOptions ) ;
1065+
1066+ expect ( results . violations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1067+ for ( const violation of results . violations ) {
1068+ expect ( violation . fixes ) . toBeUndefined ( ) ;
1069+ }
1070+ } ) ;
1071+
1072+ it ( 'When includeFixes is not specified, violations do not include fixes' , async ( ) => {
1073+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1074+ const runOptions : RunOptions = createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ;
1075+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' ] , runOptions ) ;
1076+
1077+ expect ( results . violations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1078+ for ( const violation of results . violations ) {
1079+ expect ( violation . fixes ) . toBeUndefined ( ) ;
1080+ }
1081+ } ) ;
1082+
1083+ it ( 'When includeSuggestions is true, violations with suggestions include them' , async ( ) => {
1084+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1085+ const runOptions : RunOptions = {
1086+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1087+ includeSuggestions : true
1088+ } ;
1089+ const results : EngineRunResults = await engine . runRules ( [ 'no-unused-vars' ] , runOptions ) ;
1090+
1091+ const violationsWithSuggestions = results . violations . filter ( v => v . suggestions && v . suggestions . length > 0 ) ;
1092+ for ( const violation of violationsWithSuggestions ) {
1093+ for ( const suggestion of violation . suggestions ! ) {
1094+ expect ( suggestion . message ) . toBeDefined ( ) ;
1095+ expect ( suggestion . location . file ) . toEqual ( fixableFile ) ;
1096+ expect ( suggestion . location . startLine ) . toBeGreaterThanOrEqual ( 1 ) ;
1097+ expect ( suggestion . location . startColumn ) . toBeGreaterThanOrEqual ( 1 ) ;
1098+ }
1099+ }
1100+ } ) ;
1101+
1102+ it ( 'When includeSuggestions is false, violations do not include suggestions' , async ( ) => {
1103+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1104+ const runOptions : RunOptions = {
1105+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1106+ includeSuggestions : false
1107+ } ;
1108+ const results : EngineRunResults = await engine . runRules ( [ 'no-unused-vars' ] , runOptions ) ;
1109+
1110+ for ( const violation of results . violations ) {
1111+ expect ( violation . suggestions ) . toBeUndefined ( ) ;
1112+ }
1113+ } ) ;
1114+
1115+ it ( 'When both includeFixes and includeSuggestions are true, both are populated where applicable' , async ( ) => {
1116+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1117+ const runOptions : RunOptions = {
1118+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1119+ includeFixes : true ,
1120+ includeSuggestions : true
1121+ } ;
1122+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' , 'no-unused-vars' ] , runOptions ) ;
1123+
1124+ expect ( results . violations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1125+ const hasAnyFixes = results . violations . some ( v => v . fixes && v . fixes . length > 0 ) ;
1126+ expect ( hasAnyFixes ) . toBe ( true ) ;
1127+ } ) ;
1128+
1129+ it ( 'Fix locations have correct line and column values converted from byte offsets' , async ( ) => {
1130+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1131+ const runOptions : RunOptions = {
1132+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1133+ includeFixes : true
1134+ } ;
1135+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' ] , runOptions ) ;
1136+
1137+ const preferConstViolation = results . violations . find ( v => v . ruleName === 'prefer-const' ) ;
1138+ expect ( preferConstViolation ) . toBeDefined ( ) ;
1139+ expect ( preferConstViolation ! . fixes ) . toBeDefined ( ) ;
1140+
1141+ const fix = preferConstViolation ! . fixes ! [ 0 ] ;
1142+ expect ( fix . location . startLine ) . toEqual ( 1 ) ;
1143+ expect ( fix . location . startColumn ) . toBeGreaterThanOrEqual ( 1 ) ;
1144+ expect ( fix . fixedCode ) . toContain ( 'const' ) ;
1145+ } ) ;
1146+
1147+ it ( 'Multiple violations in the same file produce fixes without errors' , async ( ) => {
1148+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1149+ const runOptions : RunOptions = {
1150+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ fixableFile ] ) ) ,
1151+ includeFixes : true
1152+ } ;
1153+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' , 'no-var' ] , runOptions ) ;
1154+
1155+ const fixableViolations = results . violations . filter ( v => v . fixes && v . fixes . length > 0 ) ;
1156+ expect ( fixableViolations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1157+ for ( const violation of fixableViolations ) {
1158+ expect ( violation . fixes ! [ 0 ] . location . file ) . toEqual ( fixableFile ) ;
1159+ }
1160+ } ) ;
1161+
1162+ it ( 'Fix locations are correct for files with multi-byte characters' , async ( ) => {
1163+ const multibyteFile : string = path . join ( workspaceWithFixableViolations , 'fixable_multibyte.js' ) ;
1164+ const engine : Engine = await createEngineFromPlugin ( DEFAULT_CONFIG_FOR_TESTING ) ;
1165+ const runOptions : RunOptions = {
1166+ ...createRunOptions ( new Workspace ( 'id' , [ workspaceWithFixableViolations ] , [ multibyteFile ] ) ) ,
1167+ includeFixes : true
1168+ } ;
1169+ const results : EngineRunResults = await engine . runRules ( [ 'prefer-const' ] , runOptions ) ;
1170+
1171+ expect ( results . violations . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1172+ const violationWithFix = results . violations . find ( v => v . fixes && v . fixes . length > 0 ) ;
1173+ expect ( violationWithFix ) . toBeDefined ( ) ;
1174+
1175+ const fix = violationWithFix ! . fixes ! [ 0 ] ;
1176+ // Fix should target line 1 (let greeting) and replace with const
1177+ expect ( fix . location . file ) . toEqual ( multibyteFile ) ;
1178+ expect ( fix . location . startLine ) . toEqual ( 1 ) ;
1179+ expect ( fix . fixedCode ) . toContain ( 'const' ) ;
1180+ // Verify the line/column values are valid positive integers
1181+ expect ( fix . location . startColumn ) . toBeGreaterThanOrEqual ( 1 ) ;
1182+ expect ( fix . location . endLine ) . toBeGreaterThanOrEqual ( 1 ) ;
1183+ expect ( fix . location . endColumn ) . toBeGreaterThanOrEqual ( 1 ) ;
1184+ } ) ;
1185+ } ) ;
1186+
10121187function loadRuleDescriptions ( fileNameFromTestDataFolder : string ) : RuleDescription [ ] {
10131188 return JSON . parse ( fs . readFileSync ( path . join ( testDataFolder ,
10141189 fileNameFromTestDataFolder ) , 'utf8' ) ) as RuleDescription [ ] ;
0 commit comments