thailint 0.2.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. src/cli.py +646 -36
  2. src/config.py +6 -2
  3. src/core/base.py +90 -5
  4. src/core/config_parser.py +31 -4
  5. src/linters/dry/block_filter.py +5 -2
  6. src/linters/dry/cache.py +46 -92
  7. src/linters/dry/config.py +17 -13
  8. src/linters/dry/duplicate_storage.py +17 -80
  9. src/linters/dry/file_analyzer.py +11 -48
  10. src/linters/dry/linter.py +5 -12
  11. src/linters/dry/python_analyzer.py +188 -37
  12. src/linters/dry/storage_initializer.py +9 -18
  13. src/linters/dry/token_hasher.py +63 -9
  14. src/linters/dry/typescript_analyzer.py +7 -5
  15. src/linters/dry/violation_filter.py +4 -1
  16. src/linters/file_header/__init__.py +24 -0
  17. src/linters/file_header/atemporal_detector.py +87 -0
  18. src/linters/file_header/config.py +66 -0
  19. src/linters/file_header/field_validator.py +69 -0
  20. src/linters/file_header/linter.py +313 -0
  21. src/linters/file_header/python_parser.py +86 -0
  22. src/linters/file_header/violation_builder.py +78 -0
  23. src/linters/file_placement/linter.py +15 -4
  24. src/linters/magic_numbers/__init__.py +48 -0
  25. src/linters/magic_numbers/config.py +82 -0
  26. src/linters/magic_numbers/context_analyzer.py +247 -0
  27. src/linters/magic_numbers/linter.py +516 -0
  28. src/linters/magic_numbers/python_analyzer.py +76 -0
  29. src/linters/magic_numbers/typescript_analyzer.py +218 -0
  30. src/linters/magic_numbers/violation_builder.py +98 -0
  31. src/linters/nesting/__init__.py +6 -2
  32. src/linters/nesting/config.py +6 -3
  33. src/linters/nesting/linter.py +8 -19
  34. src/linters/nesting/typescript_analyzer.py +1 -0
  35. src/linters/print_statements/__init__.py +53 -0
  36. src/linters/print_statements/config.py +83 -0
  37. src/linters/print_statements/linter.py +430 -0
  38. src/linters/print_statements/python_analyzer.py +155 -0
  39. src/linters/print_statements/typescript_analyzer.py +135 -0
  40. src/linters/print_statements/violation_builder.py +98 -0
  41. src/linters/srp/__init__.py +3 -3
  42. src/linters/srp/config.py +12 -6
  43. src/linters/srp/linter.py +33 -24
  44. src/orchestrator/core.py +12 -2
  45. src/templates/thailint_config_template.yaml +158 -0
  46. src/utils/project_root.py +135 -16
  47. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/METADATA +387 -81
  48. thailint-0.5.0.dist-info/RECORD +96 -0
  49. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
  50. thailint-0.2.0.dist-info/RECORD +0 -75
  51. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
  52. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,218 @@
1
+ """
2
+ Purpose: TypeScript/JavaScript magic number detection using Tree-sitter AST analysis
3
+
4
+ Scope: Tree-sitter based numeric literal detection for TypeScript and JavaScript code
5
+
6
+ Overview: Analyzes TypeScript and JavaScript code to detect numeric literals that should be
7
+ extracted to named constants. Uses Tree-sitter parser to traverse TypeScript AST and
8
+ identify numeric literal nodes with their line numbers and values. Detects acceptable
9
+ contexts such as enum definitions and UPPERCASE constant declarations to avoid false
10
+ positives. Supports both TypeScript and JavaScript files with shared detection logic.
11
+ Handles TypeScript-specific syntax including enums, const assertions, readonly properties,
12
+ arrow functions, async functions, and class methods.
13
+
14
+ Dependencies: TypeScriptBaseAnalyzer for tree-sitter parsing, tree-sitter Node type
15
+
16
+ Exports: TypeScriptMagicNumberAnalyzer class with find_numeric_literals and context detection
17
+
18
+ Interfaces: find_numeric_literals(root_node) -> list[tuple], is_enum_context(node),
19
+ is_constant_definition(node)
20
+
21
+ Implementation: Tree-sitter node traversal with visitor pattern, context-aware filtering
22
+ for acceptable numeric literal locations
23
+ """
24
+
25
+ from typing import Any
26
+
27
+ from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
28
+
29
+ # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
30
+ try:
31
+ from tree_sitter import Node
32
+
33
+ TREE_SITTER_AVAILABLE = True
34
+ except ImportError:
35
+ TREE_SITTER_AVAILABLE = False
36
+ Node = Any # type: ignore
37
+
38
+
39
+ class TypeScriptMagicNumberAnalyzer(TypeScriptBaseAnalyzer): # thailint: ignore[srp]
40
+ """Analyzes TypeScript/JavaScript code for magic numbers using Tree-sitter.
41
+
42
+ Note: Method count (11) exceeds SRP limit (8) because refactoring for A-grade
43
+ complexity requires extracting helper methods. Class maintains single responsibility
44
+ of TypeScript magic number detection - all methods support this core purpose.
45
+ """
46
+
47
+ def find_numeric_literals(self, root_node: Node) -> list[tuple[Node, float | int, int]]:
48
+ """Find all numeric literal nodes in TypeScript/JavaScript AST.
49
+
50
+ Args:
51
+ root_node: Root tree-sitter node to search from
52
+
53
+ Returns:
54
+ List of (node, value, line_number) tuples for each numeric literal
55
+ """
56
+ if not TREE_SITTER_AVAILABLE or root_node is None:
57
+ return []
58
+
59
+ literals: list[tuple[Node, float | int, int]] = []
60
+ self._collect_numeric_literals(root_node, literals)
61
+ return literals
62
+
63
+ def _collect_numeric_literals(
64
+ self, node: Node, literals: list[tuple[Node, float | int, int]]
65
+ ) -> None:
66
+ """Recursively collect numeric literals from AST.
67
+
68
+ Args:
69
+ node: Current tree-sitter node
70
+ literals: List to accumulate found literals
71
+ """
72
+ if node.type == "number":
73
+ value = self._extract_numeric_value(node)
74
+ if value is not None:
75
+ line_number = node.start_point[0] + 1
76
+ literals.append((node, value, line_number))
77
+
78
+ for child in node.children:
79
+ self._collect_numeric_literals(child, literals)
80
+
81
+ def _extract_numeric_value(self, node: Node) -> float | int | None:
82
+ """Extract numeric value from number node.
83
+
84
+ Args:
85
+ node: Tree-sitter number node
86
+
87
+ Returns:
88
+ Numeric value (int or float) or None if parsing fails
89
+ """
90
+ text = self.extract_node_text(node)
91
+ try:
92
+ # Try int first
93
+ if "." not in text and "e" not in text.lower():
94
+ return int(text, 0) # Handles hex, octal, binary
95
+ # Otherwise float
96
+ return float(text)
97
+ except (ValueError, TypeError):
98
+ return None
99
+
100
+ def is_enum_context(self, node: Node) -> bool:
101
+ """Check if numeric literal is in enum definition.
102
+
103
+ Args:
104
+ node: Numeric literal node
105
+
106
+ Returns:
107
+ True if node is within enum_declaration
108
+ """
109
+ if not TREE_SITTER_AVAILABLE:
110
+ return False
111
+
112
+ current = node.parent
113
+ while current is not None:
114
+ if current.type == "enum_declaration":
115
+ return True
116
+ current = current.parent
117
+ return False
118
+
119
+ def is_constant_definition(self, node: Node, source_code: str) -> bool:
120
+ """Check if numeric literal is in UPPERCASE constant definition.
121
+
122
+ Args:
123
+ node: Numeric literal node
124
+ source_code: Full source code to extract variable names
125
+
126
+ Returns:
127
+ True if assigned to UPPERCASE constant variable
128
+ """
129
+ if not TREE_SITTER_AVAILABLE:
130
+ return False
131
+
132
+ # Find the declaration parent
133
+ parent = self._find_declaration_parent(node)
134
+ if parent is None:
135
+ return False
136
+
137
+ # Check if identifier is UPPERCASE constant
138
+ return self._has_uppercase_identifier(parent)
139
+
140
+ def _find_declaration_parent(self, node: Node) -> Node | None:
141
+ """Find the declaration parent node.
142
+
143
+ Args:
144
+ node: Starting node
145
+
146
+ Returns:
147
+ Declaration parent or None
148
+ """
149
+ parent = node.parent
150
+ if self._is_declaration_type(parent):
151
+ return parent
152
+
153
+ # Try grandparent for nested cases
154
+ if parent is not None:
155
+ grandparent = parent.parent
156
+ if self._is_declaration_type(grandparent):
157
+ return grandparent
158
+
159
+ return None
160
+
161
+ def _is_declaration_type(self, node: Node | None) -> bool:
162
+ """Check if node is a declaration type."""
163
+ if node is None:
164
+ return False
165
+ return node.type in ("variable_declarator", "lexical_declaration", "pair")
166
+
167
+ def _has_uppercase_identifier(self, parent_node: Node) -> bool:
168
+ """Check if declaration has UPPERCASE identifier.
169
+
170
+ Args:
171
+ parent_node: Declaration parent node
172
+
173
+ Returns:
174
+ True if identifier is UPPERCASE
175
+ """
176
+ identifier_node = self._find_identifier_in_declaration(parent_node)
177
+ if identifier_node is None:
178
+ return False
179
+
180
+ identifier_text = self.extract_node_text(identifier_node)
181
+ return self._is_uppercase_constant(identifier_text)
182
+
183
+ def _find_identifier_in_declaration(self, node: Node) -> Node | None:
184
+ """Find identifier node in variable declaration.
185
+
186
+ Args:
187
+ node: Variable declarator or lexical declaration node
188
+
189
+ Returns:
190
+ Identifier node or None
191
+ """
192
+ # Walk children looking for identifier
193
+ for child in node.children:
194
+ if child.type in ("identifier", "property_identifier"):
195
+ return child
196
+ # Recursively check children
197
+ result = self._find_identifier_in_declaration(child)
198
+ if result is not None:
199
+ return result
200
+ return None
201
+
202
+ def _is_uppercase_constant(self, name: str) -> bool:
203
+ """Check if identifier is UPPERCASE constant style.
204
+
205
+ Args:
206
+ name: Identifier name
207
+
208
+ Returns:
209
+ True if name is UPPERCASE with optional underscores
210
+ """
211
+ if not name:
212
+ return False
213
+ # Must be at least one letter and all letters must be uppercase
214
+ # Allow underscores and numbers
215
+ letters_only = "".join(c for c in name if c.isalpha())
216
+ if not letters_only:
217
+ return False
218
+ return letters_only.isupper()
@@ -0,0 +1,98 @@
1
+ """
2
+ Purpose: Builds Violation objects for magic number detection
3
+
4
+ Scope: Violation message construction for magic numbers linter
5
+
6
+ Overview: Provides ViolationBuilder class that creates Violation objects for magic number detections.
7
+ Generates helpful, descriptive messages suggesting constant extraction for numeric literals.
8
+ Constructs complete Violation instances with rule_id, file_path, line number, column, message,
9
+ and suggestions. Formats messages to mention the specific numeric value and encourage using
10
+ named constants for better code maintainability and readability. Provides consistent violation
11
+ structure across all magic number detections.
12
+
13
+ Dependencies: src.core.types for Violation dataclass, pathlib for Path handling, ast for node types
14
+
15
+ Exports: ViolationBuilder class
16
+
17
+ Interfaces: ViolationBuilder.create_violation(node, value, line, file_path) -> Violation,
18
+ builds complete Violation object with all required fields
19
+
20
+ Implementation: Message template with value interpolation, structured violation construction
21
+ """
22
+
23
+ import ast
24
+ from pathlib import Path
25
+
26
+ from src.core.types import Violation
27
+
28
+
29
+ class ViolationBuilder:
30
+ """Builds violations for magic number detections."""
31
+
32
+ def __init__(self, rule_id: str) -> None:
33
+ """Initialize the violation builder.
34
+
35
+ Args:
36
+ rule_id: The rule ID to use in violations
37
+ """
38
+ self.rule_id = rule_id
39
+
40
+ def create_violation(
41
+ self,
42
+ node: ast.Constant,
43
+ value: int | float,
44
+ line: int,
45
+ file_path: Path | None,
46
+ ) -> Violation:
47
+ """Create a violation for a magic number.
48
+
49
+ Args:
50
+ node: The AST node containing the magic number
51
+ value: The numeric value
52
+ line: Line number where the violation occurs
53
+ file_path: Path to the file
54
+
55
+ Returns:
56
+ Violation object with details about the magic number
57
+ """
58
+ message = f"Magic number {value} should be a named constant"
59
+
60
+ suggestion = f"Extract {value} to a named constant (e.g., CONSTANT_NAME = {value})"
61
+
62
+ return Violation(
63
+ rule_id=self.rule_id,
64
+ file_path=str(file_path) if file_path else "",
65
+ line=line,
66
+ column=node.col_offset if hasattr(node, "col_offset") else 0,
67
+ message=message,
68
+ suggestion=suggestion,
69
+ )
70
+
71
+ def create_typescript_violation(
72
+ self,
73
+ value: int | float,
74
+ line: int,
75
+ file_path: Path | None,
76
+ ) -> Violation:
77
+ """Create a violation for a TypeScript magic number.
78
+
79
+ Args:
80
+ value: The numeric value
81
+ line: Line number where the violation occurs
82
+ file_path: Path to the file
83
+
84
+ Returns:
85
+ Violation object with details about the magic number
86
+ """
87
+ message = f"Magic number {value} should be a named constant"
88
+
89
+ suggestion = f"Extract {value} to a named constant (e.g., const CONSTANT_NAME = {value})"
90
+
91
+ return Violation(
92
+ rule_id=self.rule_id,
93
+ file_path=str(file_path) if file_path else "",
94
+ line=line,
95
+ column=0, # Tree-sitter nodes don't have easy column access
96
+ message=message,
97
+ suggestion=suggestion,
98
+ )
@@ -22,7 +22,7 @@ Implementation: Simple re-export pattern for package interface, convenience func
22
22
  from pathlib import Path
23
23
  from typing import Any
24
24
 
25
- from .config import NestingConfig
25
+ from .config import DEFAULT_MAX_NESTING_DEPTH, NestingConfig
26
26
  from .linter import NestingDepthRule
27
27
  from .python_analyzer import PythonNestingAnalyzer
28
28
  from .typescript_analyzer import TypeScriptNestingAnalyzer
@@ -36,7 +36,11 @@ __all__ = [
36
36
  ]
37
37
 
38
38
 
39
- def lint(path: Path | str, config: dict[str, Any] | None = None, max_depth: int = 4) -> list:
39
+ def lint(
40
+ path: Path | str,
41
+ config: dict[str, Any] | None = None,
42
+ max_depth: int = DEFAULT_MAX_NESTING_DEPTH,
43
+ ) -> list:
40
44
  """Lint a file or directory for nesting depth violations.
41
45
 
42
46
  Args:
@@ -21,12 +21,15 @@ Implementation: Dataclass with validation and defaults, matches reference implem
21
21
  from dataclasses import dataclass
22
22
  from typing import Any
23
23
 
24
+ # Default nesting threshold constant
25
+ DEFAULT_MAX_NESTING_DEPTH = 4
26
+
24
27
 
25
28
  @dataclass
26
29
  class NestingConfig:
27
30
  """Configuration for nesting depth linter."""
28
31
 
29
- max_nesting_depth: int = 4 # Default from reference implementation
32
+ max_nesting_depth: int = DEFAULT_MAX_NESTING_DEPTH # Default from reference implementation
30
33
  enabled: bool = True
31
34
 
32
35
  def __post_init__(self) -> None:
@@ -49,10 +52,10 @@ class NestingConfig:
49
52
  if language and language in config:
50
53
  lang_config = config[language]
51
54
  max_nesting_depth = lang_config.get(
52
- "max_nesting_depth", config.get("max_nesting_depth", 4)
55
+ "max_nesting_depth", config.get("max_nesting_depth", DEFAULT_MAX_NESTING_DEPTH)
53
56
  )
54
57
  else:
55
- max_nesting_depth = config.get("max_nesting_depth", 4)
58
+ max_nesting_depth = config.get("max_nesting_depth", DEFAULT_MAX_NESTING_DEPTH)
56
59
 
57
60
  return cls(
58
61
  max_nesting_depth=max_nesting_depth,
@@ -21,8 +21,8 @@ Implementation: Composition pattern with helper classes, AST-based analysis with
21
21
  import ast
22
22
  from typing import Any
23
23
 
24
- from src.core.base import BaseLintContext, BaseLintRule
25
- from src.core.linter_utils import has_file_content, load_linter_config
24
+ from src.core.base import BaseLintContext, MultiLanguageLintRule
25
+ from src.core.linter_utils import load_linter_config
26
26
  from src.core.types import Violation
27
27
  from src.linter_config.ignore import IgnoreDirectiveParser
28
28
 
@@ -32,7 +32,7 @@ from .typescript_analyzer import TypeScriptNestingAnalyzer
32
32
  from .violation_builder import NestingViolationBuilder
33
33
 
34
34
 
35
- class NestingDepthRule(BaseLintRule):
35
+ class NestingDepthRule(MultiLanguageLintRule):
36
36
  """Detects excessive nesting depth in functions."""
37
37
 
38
38
  def __init__(self) -> None:
@@ -55,27 +55,16 @@ class NestingDepthRule(BaseLintRule):
55
55
  """Description of what this rule checks."""
56
56
  return "Functions should not have excessive nesting depth for better readability"
57
57
 
58
- def check(self, context: BaseLintContext) -> list[Violation]:
59
- """Check for excessive nesting depth violations.
58
+ def _load_config(self, context: BaseLintContext) -> NestingConfig:
59
+ """Load configuration from context.
60
60
 
61
61
  Args:
62
- context: Lint context with file information
62
+ context: Lint context
63
63
 
64
64
  Returns:
65
- List of violations found
65
+ NestingConfig instance
66
66
  """
67
- if not has_file_content(context):
68
- return []
69
-
70
- config = load_linter_config(context, "nesting", NestingConfig)
71
- if not config.enabled:
72
- return []
73
-
74
- if context.language == "python":
75
- return self._check_python(context, config)
76
- if context.language in ("typescript", "javascript"):
77
- return self._check_typescript(context, config)
78
- return []
67
+ return load_linter_config(context, "nesting", NestingConfig)
79
68
 
80
69
  def _process_python_functions(
81
70
  self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
@@ -22,6 +22,7 @@ from typing import Any
22
22
 
23
23
  from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
24
24
 
25
+ # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
25
26
  try:
26
27
  from tree_sitter import Node
27
28
 
@@ -0,0 +1,53 @@
1
+ """
2
+ File: src/linters/print_statements/__init__.py
3
+
4
+ Purpose: Print statements linter package exports and convenience functions
5
+
6
+ Exports: PrintStatementRule class, PrintStatementConfig dataclass, lint() convenience function
7
+
8
+ Depends: .linter for PrintStatementRule, .config for PrintStatementConfig
9
+
10
+ Implements: lint(file_path, config) -> list[Violation] for simple linting operations
11
+
12
+ Related: src/linters/magic_numbers/__init__.py, src/core/base.py
13
+
14
+ Overview: Provides the public interface for the print statements linter package. Exports main
15
+ PrintStatementRule class for use by the orchestrator and PrintStatementConfig for configuration.
16
+ Includes lint() convenience function that provides a simple API for running the print statements
17
+ linter on a file without directly interacting with the orchestrator. This module serves as the
18
+ entry point for users of the print statements linter, hiding implementation details and exposing
19
+ only the essential components needed for linting operations.
20
+
21
+ Usage: from src.linters.print_statements import PrintStatementRule, lint
22
+ violations = lint("path/to/file.py")
23
+
24
+ Notes: Module-level exports with __all__ definition, convenience function wrapper
25
+ """
26
+
27
+ from .config import PrintStatementConfig
28
+ from .linter import PrintStatementRule
29
+
30
+ __all__ = ["PrintStatementRule", "PrintStatementConfig", "lint"]
31
+
32
+
33
+ def lint(file_path: str, config: dict | None = None) -> list:
34
+ """Convenience function for linting a file for print statements.
35
+
36
+ Args:
37
+ file_path: Path to the file to lint
38
+ config: Optional configuration dictionary
39
+
40
+ Returns:
41
+ List of violations found
42
+ """
43
+ from pathlib import Path
44
+
45
+ from src.orchestrator.core import FileLintContext
46
+
47
+ rule = PrintStatementRule()
48
+ context = FileLintContext(
49
+ path=Path(file_path),
50
+ lang="python",
51
+ )
52
+
53
+ return rule.check(context)
@@ -0,0 +1,83 @@
1
+ """
2
+ File: src/linters/print_statements/config.py
3
+
4
+ Purpose: Configuration schema for print statements linter
5
+
6
+ Exports: PrintStatementConfig dataclass
7
+
8
+ Depends: dataclasses, typing
9
+
10
+ Implements: PrintStatementConfig(enabled, ignore, allow_in_scripts, console_methods),
11
+ from_dict class method for loading configuration from dictionary
12
+
13
+ Related: src/linters/magic_numbers/config.py, src/core/types.py
14
+
15
+ Overview: Defines configuration schema for print statements linter. Provides PrintStatementConfig
16
+ dataclass with enabled flag, ignore patterns list, allow_in_scripts setting (default True to
17
+ allow print in __main__ blocks), and console_methods set (default includes log, warn, error,
18
+ debug, info) for TypeScript/JavaScript console method detection. Supports per-file and
19
+ per-directory config overrides through from_dict class method. Integrates with orchestrator's
20
+ configuration system to allow users to customize detection via .thailint.yaml configuration.
21
+
22
+ Usage: config = PrintStatementConfig.from_dict(yaml_config, language="python")
23
+
24
+ Notes: Dataclass with defaults matching common use cases, language-specific override support
25
+ """
26
+
27
+ from dataclasses import dataclass, field
28
+ from typing import Any
29
+
30
+
31
+ @dataclass
32
+ class PrintStatementConfig:
33
+ """Configuration for print statements linter."""
34
+
35
+ enabled: bool = True
36
+ ignore: list[str] = field(default_factory=list)
37
+ allow_in_scripts: bool = True
38
+ console_methods: set[str] = field(
39
+ default_factory=lambda: {"log", "warn", "error", "debug", "info"}
40
+ )
41
+
42
+ @classmethod
43
+ def from_dict(
44
+ cls, config: dict[str, Any], language: str | None = None
45
+ ) -> "PrintStatementConfig":
46
+ """Load configuration from dictionary with language-specific overrides.
47
+
48
+ Args:
49
+ config: Dictionary containing configuration values
50
+ language: Programming language (python, typescript, javascript)
51
+ for language-specific settings
52
+
53
+ Returns:
54
+ PrintStatementConfig instance with values from dictionary
55
+ """
56
+ # Get language-specific config if available
57
+ if language and language in config:
58
+ lang_config = config[language]
59
+ allow_in_scripts = lang_config.get(
60
+ "allow_in_scripts", config.get("allow_in_scripts", True)
61
+ )
62
+ console_methods = set(
63
+ lang_config.get(
64
+ "console_methods",
65
+ config.get("console_methods", ["log", "warn", "error", "debug", "info"]),
66
+ )
67
+ )
68
+ else:
69
+ allow_in_scripts = config.get("allow_in_scripts", True)
70
+ console_methods = set(
71
+ config.get("console_methods", ["log", "warn", "error", "debug", "info"])
72
+ )
73
+
74
+ ignore_patterns = config.get("ignore", [])
75
+ if not isinstance(ignore_patterns, list):
76
+ ignore_patterns = []
77
+
78
+ return cls(
79
+ enabled=config.get("enabled", True),
80
+ ignore=ignore_patterns,
81
+ allow_in_scripts=allow_in_scripts,
82
+ console_methods=console_methods,
83
+ )