Coverage for lintro / tools / implementations / ruff / commands.py: 84%

67 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Ruff command building utilities. 

2 

3Functions for building ruff check and format command line arguments. 

4""" 

5 

6import os 

7from typing import TYPE_CHECKING, Any 

8 

9from lintro.enums.env_bool import EnvBool 

10 

11if TYPE_CHECKING: 

12 from lintro.tools.definitions.ruff import RuffPlugin 

13 

14 

15def _get_list_option(options: dict[str, Any], key: str) -> list[str]: 

16 """Get a list option from options dict, returning empty list if not set. 

17 

18 Args: 

19 options: Dictionary of options to retrieve from. 

20 key: Key to look up in the options dictionary. 

21 

22 Returns: 

23 List of string values, or empty list if key not found. 

24 """ 

25 value = options.get(key) 

26 if value is None: 

27 return [] 

28 # Handle single string value 

29 if isinstance(value, str): 

30 return [value] 

31 # Handle list, tuple, set, or other iterables 

32 try: 

33 return [str(item) for item in value] 

34 except TypeError: 

35 # Non-iterable scalar value 

36 return [str(value)] 

37 

38 

39def _get_set_option(options: dict[str, Any], key: str) -> set[str]: 

40 """Get a set option from options dict, returning empty set if not set. 

41 

42 Args: 

43 options: dict[str, Any]: Dictionary of options to retrieve from. 

44 key: str: Key to look up in the options dictionary. 

45 

46 Returns: 

47 set[str]: Set of string values, or empty set if key not found. 

48 """ 

49 return set(_get_list_option(options, key)) 

50 

51 

52# Constants from tool_ruff.py 

53RUFF_OUTPUT_FORMAT: str = "json" 

54 

55 

56def build_ruff_check_command( 

57 tool: "RuffPlugin", 

58 files: list[str], 

59 fix: bool = False, 

60) -> list[str]: 

61 """Build the ruff check command. 

62 

63 Args: 

64 tool: RuffTool instance 

65 files: list[str]: List of files to check. 

66 fix: bool: Whether to apply fixes. 

67 

68 Returns: 

69 list[str]: List of command arguments. 

70 """ 

71 cmd: list[str] = tool._get_executable_command(tool_name="ruff") + ["check"] 

72 

73 # Get enforced settings to avoid duplicate CLI args 

74 enforced = tool._get_enforced_settings() 

75 

76 # Add Lintro config injection args (--line-length, --target-version) 

77 # from enforce tier. This takes precedence over native config and options 

78 config_args = tool._build_config_args() 

79 if config_args: 

80 cmd.extend(config_args) 

81 # Add --isolated if in test mode (fallback when no Lintro config) 

82 elif os.environ.get("LINTRO_TEST_MODE") == EnvBool.TRUE: 

83 cmd.append("--isolated") 

84 

85 # Add configuration options 

86 selected_rules = _get_list_option(tool.options, "select") 

87 ignored_rules = _get_set_option(tool.options, "ignore") 

88 extend_selected_rules = _get_list_option(tool.options, "extend_select") 

89 

90 # Ensure E501 is included when selecting E-family 

91 # Check in selected_rules, extend_selected_rules, and ignored_rules 

92 has_e_family = ("E" in selected_rules) or ("E" in extend_selected_rules) 

93 

94 # Add E501 when E-family is present and E501 not already ignored 

95 # or in selected/extend 

96 if ( 

97 has_e_family 

98 and "E501" not in ignored_rules 

99 and "E501" not in selected_rules 

100 and "E501" not in extend_selected_rules 

101 ): 

102 extend_selected_rules.append("E501") 

103 

104 if selected_rules: 

105 cmd.extend(["--select", ",".join(selected_rules)]) 

106 if ignored_rules: 

107 cmd.extend(["--ignore", ",".join(sorted(ignored_rules))]) 

108 if extend_selected_rules: 

109 cmd.extend(["--extend-select", ",".join(extend_selected_rules)]) 

110 extend_ignored_rules = _get_list_option(tool.options, "extend_ignore") 

111 if extend_ignored_rules: 

112 cmd.extend(["--extend-ignore", ",".join(extend_ignored_rules)]) 

113 # Only add line_length/target_version from options if not enforced. 

114 # Note: enforced uses Lintro's generic names (line_length, target_python) 

115 # while options use tool-specific names (line_length, target_version). 

116 if tool.options.get("line_length") and "line_length" not in enforced: 

117 cmd.extend(["--line-length", str(tool.options["line_length"])]) 

118 if tool.options.get("target_version") and "target_python" not in enforced: 

119 cmd.extend(["--target-version", str(tool.options["target_version"])]) 

120 

121 # Fix options 

122 if fix: 

123 cmd.append("--fix") 

124 if tool.options.get("unsafe_fixes"): 

125 cmd.append("--unsafe-fixes") 

126 if tool.options.get("show_fixes"): 

127 cmd.append("--show-fixes") 

128 if tool.options.get("fix_only"): 

129 cmd.append("--fix-only") 

130 

131 # Output format 

132 cmd.extend(["--output-format", RUFF_OUTPUT_FORMAT]) 

133 

134 # Add files 

135 cmd.extend(files) 

136 

137 return cmd 

138 

139 

140def build_ruff_format_command( 

141 tool: "RuffPlugin", 

142 files: list[str], 

143 check_only: bool = False, 

144) -> list[str]: 

145 """Build the ruff format command. 

146 

147 Args: 

148 tool: RuffTool instance 

149 files: list[str]: List of files to format. 

150 check_only: bool: Whether to only check formatting without applying changes. 

151 

152 Returns: 

153 list[str]: List of command arguments. 

154 """ 

155 cmd: list[str] = tool._get_executable_command(tool_name="ruff") + ["format"] 

156 

157 if check_only: 

158 cmd.append("--check") 

159 

160 # Add Lintro config injection args (--isolated, --config) 

161 config_args = tool._build_config_args() 

162 if config_args: 

163 cmd.extend(config_args) 

164 else: 

165 # Fallback to options-based configuration 

166 if tool.options.get("line_length"): 

167 cmd.extend(["--line-length", str(tool.options["line_length"])]) 

168 if tool.options.get("target_version"): 

169 cmd.extend(["--target-version", str(tool.options["target_version"])]) 

170 

171 # Add files 

172 cmd.extend(files) 

173 

174 return cmd