thailint 0.5.0__py3-none-any.whl → 0.15.3__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.
- src/__init__.py +1 -0
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/rust_base.py +155 -0
- src/analyzers/rust_context.py +141 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +30 -0
- src/cli/__main__.py +22 -0
- src/cli/config.py +480 -0
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +67 -0
- src/cli/linters/code_patterns.py +270 -0
- src/cli/linters/code_smells.py +342 -0
- src/cli/linters/documentation.py +83 -0
- src/cli/linters/performance.py +287 -0
- src/cli/linters/shared.py +331 -0
- src/cli/linters/structure.py +327 -0
- src/cli/linters/structure_quality.py +328 -0
- src/cli/main.py +120 -0
- src/cli/utils.py +395 -0
- src/cli_main.py +37 -0
- src/config.py +38 -25
- src/core/base.py +7 -2
- src/core/cli_utils.py +19 -2
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +95 -6
- src/core/python_lint_rule.py +101 -0
- src/core/registry.py +1 -1
- src/core/rule_discovery.py +147 -84
- src/core/types.py +13 -0
- src/core/violation_builder.py +78 -15
- src/core/violation_utils.py +69 -0
- src/formatters/__init__.py +22 -0
- src/formatters/sarif.py +202 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +254 -395
- src/linter_config/loader.py +45 -12
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/__init__.py +90 -0
- src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- src/linters/collection_pipeline/ast_utils.py +40 -0
- src/linters/collection_pipeline/config.py +75 -0
- src/linters/collection_pipeline/continue_analyzer.py +94 -0
- src/linters/collection_pipeline/detector.py +360 -0
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +420 -0
- src/linters/collection_pipeline/suggestion_builder.py +130 -0
- src/linters/cqs/__init__.py +54 -0
- src/linters/cqs/config.py +55 -0
- src/linters/cqs/function_analyzer.py +201 -0
- src/linters/cqs/input_detector.py +139 -0
- src/linters/cqs/linter.py +159 -0
- src/linters/cqs/output_detector.py +84 -0
- src/linters/cqs/python_analyzer.py +54 -0
- src/linters/cqs/types.py +82 -0
- src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- src/linters/cqs/typescript_function_analyzer.py +192 -0
- src/linters/cqs/typescript_input_detector.py +203 -0
- src/linters/cqs/typescript_output_detector.py +117 -0
- src/linters/cqs/violation_builder.py +94 -0
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +120 -20
- src/linters/dry/block_grouper.py +4 -0
- src/linters/dry/cache.py +104 -10
- src/linters/dry/cache_query.py +4 -0
- src/linters/dry/config.py +54 -11
- src/linters/dry/constant.py +92 -0
- src/linters/dry/constant_matcher.py +223 -0
- src/linters/dry/constant_violation_builder.py +98 -0
- src/linters/dry/duplicate_storage.py +5 -4
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +183 -48
- src/linters/dry/python_analyzer.py +60 -439
- src/linters/dry/python_constant_extractor.py +100 -0
- src/linters/dry/single_statement_detector.py +417 -0
- src/linters/dry/token_hasher.py +116 -112
- src/linters/dry/typescript_analyzer.py +68 -382
- src/linters/dry/typescript_constant_extractor.py +138 -0
- src/linters/dry/typescript_statement_detector.py +255 -0
- src/linters/dry/typescript_value_extractor.py +70 -0
- src/linters/dry/violation_builder.py +4 -0
- src/linters/dry/violation_filter.py +5 -4
- src/linters/dry/violation_generator.py +71 -14
- src/linters/file_header/atemporal_detector.py +68 -50
- src/linters/file_header/base_parser.py +93 -0
- src/linters/file_header/bash_parser.py +66 -0
- src/linters/file_header/config.py +90 -16
- src/linters/file_header/css_parser.py +70 -0
- src/linters/file_header/field_validator.py +36 -33
- src/linters/file_header/linter.py +140 -144
- src/linters/file_header/markdown_parser.py +130 -0
- src/linters/file_header/python_parser.py +14 -58
- src/linters/file_header/typescript_parser.py +73 -0
- src/linters/file_header/violation_builder.py +13 -12
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/directory_matcher.py +4 -0
- src/linters/file_placement/linter.py +66 -34
- src/linters/file_placement/pattern_matcher.py +41 -6
- src/linters/file_placement/pattern_validator.py +31 -12
- src/linters/file_placement/rule_checker.py +12 -7
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +74 -0
- src/linters/lazy_ignores/directive_utils.py +164 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +168 -0
- src/linters/lazy_ignores/python_analyzer.py +209 -0
- src/linters/lazy_ignores/rule_id_utils.py +180 -0
- src/linters/lazy_ignores/skip_detector.py +298 -0
- src/linters/lazy_ignores/types.py +71 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +135 -0
- src/linters/lbyl/__init__.py +31 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/linter.py +67 -0
- src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- src/linters/lbyl/pattern_detectors/base.py +63 -0
- src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- src/linters/lbyl/python_analyzer.py +215 -0
- src/linters/lbyl/violation_builder.py +354 -0
- src/linters/magic_numbers/context_analyzer.py +227 -225
- src/linters/magic_numbers/linter.py +28 -82
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -12
- src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- src/linters/method_property/__init__.py +49 -0
- src/linters/method_property/config.py +138 -0
- src/linters/method_property/linter.py +414 -0
- src/linters/method_property/python_analyzer.py +473 -0
- src/linters/method_property/violation_builder.py +119 -0
- src/linters/nesting/linter.py +24 -16
- src/linters/nesting/python_analyzer.py +4 -0
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/violation_builder.py +1 -0
- src/linters/performance/__init__.py +91 -0
- src/linters/performance/config.py +43 -0
- src/linters/performance/constants.py +49 -0
- src/linters/performance/linter.py +149 -0
- src/linters/performance/python_analyzer.py +365 -0
- src/linters/performance/regex_analyzer.py +312 -0
- src/linters/performance/regex_linter.py +139 -0
- src/linters/performance/typescript_analyzer.py +236 -0
- src/linters/performance/violation_builder.py +160 -0
- src/linters/print_statements/config.py +7 -12
- src/linters/print_statements/linter.py +26 -43
- src/linters/print_statements/python_analyzer.py +91 -93
- src/linters/print_statements/typescript_analyzer.py +15 -25
- src/linters/print_statements/violation_builder.py +12 -14
- src/linters/srp/class_analyzer.py +11 -7
- src/linters/srp/heuristics.py +56 -22
- src/linters/srp/linter.py +15 -16
- src/linters/srp/python_analyzer.py +55 -20
- src/linters/srp/typescript_metrics_calculator.py +110 -50
- src/linters/stateless_class/__init__.py +25 -0
- src/linters/stateless_class/config.py +58 -0
- src/linters/stateless_class/linter.py +349 -0
- src/linters/stateless_class/python_analyzer.py +290 -0
- src/linters/stringly_typed/__init__.py +36 -0
- src/linters/stringly_typed/config.py +189 -0
- src/linters/stringly_typed/context_filter.py +451 -0
- src/linters/stringly_typed/function_call_violation_builder.py +135 -0
- src/linters/stringly_typed/ignore_checker.py +100 -0
- src/linters/stringly_typed/ignore_utils.py +51 -0
- src/linters/stringly_typed/linter.py +376 -0
- src/linters/stringly_typed/python/__init__.py +33 -0
- src/linters/stringly_typed/python/analyzer.py +348 -0
- src/linters/stringly_typed/python/call_tracker.py +175 -0
- src/linters/stringly_typed/python/comparison_tracker.py +257 -0
- src/linters/stringly_typed/python/condition_extractor.py +134 -0
- src/linters/stringly_typed/python/conditional_detector.py +179 -0
- src/linters/stringly_typed/python/constants.py +21 -0
- src/linters/stringly_typed/python/match_analyzer.py +94 -0
- src/linters/stringly_typed/python/validation_detector.py +189 -0
- src/linters/stringly_typed/python/variable_extractor.py +96 -0
- src/linters/stringly_typed/storage.py +620 -0
- src/linters/stringly_typed/storage_initializer.py +45 -0
- src/linters/stringly_typed/typescript/__init__.py +28 -0
- src/linters/stringly_typed/typescript/analyzer.py +157 -0
- src/linters/stringly_typed/typescript/call_tracker.py +335 -0
- src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
- src/linters/stringly_typed/violation_generator.py +419 -0
- src/orchestrator/core.py +252 -14
- src/orchestrator/language_detector.py +5 -3
- src/templates/thailint_config_template.yaml +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.15.3.dist-info/METADATA +187 -0
- thailint-0.15.3.dist-info/RECORD +226 -0
- thailint-0.15.3.dist-info/entry_points.txt +4 -0
- src/cli.py +0 -1665
- thailint-0.5.0.dist-info/METADATA +0 -1286
- thailint-0.5.0.dist-info/RECORD +0 -96
- thailint-0.5.0.dist-info/entry_points.txt +0 -4
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Base class for file header parsers with common field parsing logic
|
|
3
|
+
|
|
4
|
+
Scope: File header parsing infrastructure for all language-specific parsers
|
|
5
|
+
|
|
6
|
+
Overview: Provides common field parsing functionality shared across all language-specific
|
|
7
|
+
header parsers. Implements the parse_fields method and helper methods for
|
|
8
|
+
detecting field lines and saving fields. Uses template method pattern where subclasses
|
|
9
|
+
implement extract_header for language-specific header extraction while this base class
|
|
10
|
+
handles field parsing logic. Supports multi-line field values and field continuation.
|
|
11
|
+
|
|
12
|
+
Dependencies: re module for field pattern matching, abc module for abstract base class
|
|
13
|
+
|
|
14
|
+
Exports: BaseHeaderParser abstract base class
|
|
15
|
+
|
|
16
|
+
Interfaces: extract_header(code) abstract method, parse_fields(header) -> dict[str, str] for field extraction
|
|
17
|
+
|
|
18
|
+
Implementation: Template method pattern with shared field parsing and language-specific extraction
|
|
19
|
+
|
|
20
|
+
Suppressions:
|
|
21
|
+
- nesting: parse_fields uses nested loops for multi-line field processing. Extracting
|
|
22
|
+
would fragment the field-building logic without improving clarity.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from abc import ABC, abstractmethod
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseHeaderParser(ABC):
|
|
30
|
+
"""Base class for file header parsers with common field parsing logic."""
|
|
31
|
+
|
|
32
|
+
# Pattern to match field names (word characters and /)
|
|
33
|
+
FIELD_NAME_PATTERN = re.compile(r"^[\w/]+$")
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def extract_header(self, code: str) -> str | None:
|
|
37
|
+
"""Extract header from source code.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
code: Source code
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Header content or None if not found
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def parse_fields(self, header: str) -> dict[str, str]: # thailint: ignore[nesting]
|
|
47
|
+
"""Parse structured fields from header text.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
header: Header text
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dictionary mapping field_name -> field_value
|
|
54
|
+
"""
|
|
55
|
+
fields: dict[str, str] = {}
|
|
56
|
+
current_field: str | None = None
|
|
57
|
+
current_value: list[str] = []
|
|
58
|
+
|
|
59
|
+
for line in header.split("\n"):
|
|
60
|
+
if self._is_field_line(line):
|
|
61
|
+
self._save_field(fields, current_field, current_value)
|
|
62
|
+
current_field, current_value = self._start_new_field(line)
|
|
63
|
+
elif current_field:
|
|
64
|
+
current_value.append(line.strip())
|
|
65
|
+
|
|
66
|
+
self._save_field(fields, current_field, current_value)
|
|
67
|
+
return fields
|
|
68
|
+
|
|
69
|
+
def _is_field_line(self, line: str) -> bool:
|
|
70
|
+
"""Check if line starts a new field."""
|
|
71
|
+
if ":" not in line:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
colon_pos = line.find(":")
|
|
75
|
+
if colon_pos <= 0:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
field_name = line[:colon_pos].strip()
|
|
79
|
+
return bool(self.FIELD_NAME_PATTERN.match(field_name))
|
|
80
|
+
|
|
81
|
+
def _start_new_field(self, line: str) -> tuple[str, list[str]]:
|
|
82
|
+
"""Parse a field line and start tracking its value."""
|
|
83
|
+
parts = line.split(":", 1)
|
|
84
|
+
field_name = parts[0].strip()
|
|
85
|
+
initial_value = parts[1].strip() if len(parts) > 1 else ""
|
|
86
|
+
return field_name, [initial_value] if initial_value else []
|
|
87
|
+
|
|
88
|
+
def _save_field(
|
|
89
|
+
self, fields: dict[str, str], field_name: str | None, value_lines: list[str]
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Save accumulated field value to fields dict."""
|
|
92
|
+
if field_name:
|
|
93
|
+
fields[field_name] = "\n".join(value_lines).strip()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Bash shell script comment header extraction and parsing
|
|
3
|
+
|
|
4
|
+
Scope: Bash and shell script file header parsing
|
|
5
|
+
|
|
6
|
+
Overview: Extracts hash comment headers from Bash shell scripts. Handles shebang lines
|
|
7
|
+
(#!/bin/bash, #!/usr/bin/env bash, etc.) by skipping them and extracting the
|
|
8
|
+
comment block that follows. Parses structured header fields from comment content.
|
|
9
|
+
Extracts contiguous comment blocks from the start of the file and processes them
|
|
10
|
+
into structured fields for validation.
|
|
11
|
+
|
|
12
|
+
Dependencies: base_parser.BaseHeaderParser for common field parsing functionality
|
|
13
|
+
|
|
14
|
+
Exports: BashHeaderParser class
|
|
15
|
+
|
|
16
|
+
Interfaces: extract_header(code) -> str | None for comment extraction, parse_fields(header) inherited from base
|
|
17
|
+
|
|
18
|
+
Implementation: Skips shebang and preamble, then extracts contiguous hash comment block
|
|
19
|
+
|
|
20
|
+
Suppressions:
|
|
21
|
+
- nesting: _skip_preamble uses conditional loops for shebang/preamble detection.
|
|
22
|
+
Sequential line processing requires nested state checks.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from src.linters.file_header.base_parser import BaseHeaderParser
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BashHeaderParser(BaseHeaderParser):
|
|
29
|
+
"""Extracts and parses Bash file headers from comment blocks."""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
"""Initialize the Bash header parser."""
|
|
33
|
+
pass # BaseHeaderParser has no __init__, but we need this for stateless-class
|
|
34
|
+
|
|
35
|
+
def extract_header(self, code: str) -> str | None:
|
|
36
|
+
"""Extract comment header from Bash script."""
|
|
37
|
+
if not code or not code.strip():
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
lines = self._skip_preamble(code.split("\n"))
|
|
41
|
+
header_lines = self._extract_comment_block(lines)
|
|
42
|
+
|
|
43
|
+
return "\n".join(header_lines) if header_lines else None
|
|
44
|
+
|
|
45
|
+
def _skip_preamble(self, lines: list[str]) -> list[str]: # thailint: ignore[nesting]
|
|
46
|
+
"""Skip shebang and leading empty lines."""
|
|
47
|
+
result = []
|
|
48
|
+
skipping = True
|
|
49
|
+
for line in lines:
|
|
50
|
+
stripped = line.strip()
|
|
51
|
+
if skipping:
|
|
52
|
+
if stripped.startswith("#!") or not stripped:
|
|
53
|
+
continue
|
|
54
|
+
skipping = False
|
|
55
|
+
result.append(stripped)
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
def _extract_comment_block(self, lines: list[str]) -> list[str]:
|
|
59
|
+
"""Extract contiguous comment lines from start of input."""
|
|
60
|
+
result = []
|
|
61
|
+
for line in lines:
|
|
62
|
+
if line.startswith("#"):
|
|
63
|
+
result.append(line[1:].strip())
|
|
64
|
+
else:
|
|
65
|
+
break
|
|
66
|
+
return result
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
|
-
File: src/linters/file_header/config.py
|
|
3
2
|
Purpose: Configuration model for file header linter
|
|
4
|
-
Exports: FileHeaderConfig dataclass
|
|
5
|
-
Depends: dataclasses, pathlib
|
|
6
|
-
Implements: Configuration with validation and defaults
|
|
7
|
-
Related: linter.py for configuration usage
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
Scope: File header linter configuration for all supported languages
|
|
5
|
+
|
|
6
|
+
Overview: Defines configuration structure for file header linter including required fields
|
|
11
7
|
per language, ignore patterns, and validation options. Provides defaults matching
|
|
12
|
-
|
|
8
|
+
FILE_HEADER_STANDARDS.md requirements and supports loading from .thailint.yaml configuration.
|
|
9
|
+
Supports Python, TypeScript/JavaScript, Bash, Markdown, and CSS file types with
|
|
10
|
+
language-specific required field sets. Includes atemporal language enforcement
|
|
11
|
+
configuration and file ignore patterns.
|
|
12
|
+
|
|
13
|
+
Dependencies: dataclasses module for configuration structure
|
|
14
|
+
|
|
15
|
+
Exports: FileHeaderConfig dataclass
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
config = FileHeaderConfig()
|
|
16
|
-
config = FileHeaderConfig.from_dict(config_dict, "python")
|
|
17
|
+
Interfaces: from_dict(config_dict, language) -> FileHeaderConfig for configuration loading
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
Implementation: Dataclass with language-specific field lists and factory method for config loading
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
from dataclasses import dataclass, field
|
|
@@ -25,7 +26,7 @@ from dataclasses import dataclass, field
|
|
|
25
26
|
class FileHeaderConfig:
|
|
26
27
|
"""Configuration for file header linting."""
|
|
27
28
|
|
|
28
|
-
# Required fields by language
|
|
29
|
+
# Required fields by language - Python
|
|
29
30
|
required_fields_python: list[str] = field(
|
|
30
31
|
default_factory=lambda: [
|
|
31
32
|
"Purpose",
|
|
@@ -38,6 +39,56 @@ class FileHeaderConfig:
|
|
|
38
39
|
]
|
|
39
40
|
)
|
|
40
41
|
|
|
42
|
+
# Required fields by language - TypeScript/JavaScript
|
|
43
|
+
required_fields_typescript: list[str] = field(
|
|
44
|
+
default_factory=lambda: [
|
|
45
|
+
"Purpose",
|
|
46
|
+
"Scope",
|
|
47
|
+
"Overview",
|
|
48
|
+
"Dependencies",
|
|
49
|
+
"Exports",
|
|
50
|
+
"Props/Interfaces",
|
|
51
|
+
"State/Behavior",
|
|
52
|
+
]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Required fields by language - Bash
|
|
56
|
+
required_fields_bash: list[str] = field(
|
|
57
|
+
default_factory=lambda: [
|
|
58
|
+
"Purpose",
|
|
59
|
+
"Scope",
|
|
60
|
+
"Overview",
|
|
61
|
+
"Dependencies",
|
|
62
|
+
"Exports",
|
|
63
|
+
"Usage",
|
|
64
|
+
"Environment",
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Required fields by language - Markdown (lowercase for YAML frontmatter)
|
|
69
|
+
required_fields_markdown: list[str] = field(
|
|
70
|
+
default_factory=lambda: [
|
|
71
|
+
"purpose",
|
|
72
|
+
"scope",
|
|
73
|
+
"overview",
|
|
74
|
+
"audience",
|
|
75
|
+
"status",
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Required fields by language - CSS
|
|
80
|
+
required_fields_css: list[str] = field(
|
|
81
|
+
default_factory=lambda: [
|
|
82
|
+
"Purpose",
|
|
83
|
+
"Scope",
|
|
84
|
+
"Overview",
|
|
85
|
+
"Dependencies",
|
|
86
|
+
"Exports",
|
|
87
|
+
"Interfaces",
|
|
88
|
+
"Environment",
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
|
|
41
92
|
# Enforce atemporal language checking
|
|
42
93
|
enforce_atemporal: bool = True
|
|
43
94
|
|
|
@@ -57,10 +108,33 @@ class FileHeaderConfig:
|
|
|
57
108
|
Returns:
|
|
58
109
|
FileHeaderConfig instance with values from dictionary
|
|
59
110
|
"""
|
|
111
|
+
defaults = cls()
|
|
112
|
+
required_fields = config_dict.get("required_fields", {})
|
|
113
|
+
|
|
114
|
+
# Handle both list format (applies to all languages) and dict format (language-specific)
|
|
115
|
+
if isinstance(required_fields, list):
|
|
116
|
+
# Simple list format: apply same fields to all languages
|
|
117
|
+
return cls(
|
|
118
|
+
required_fields_python=required_fields,
|
|
119
|
+
required_fields_typescript=required_fields,
|
|
120
|
+
required_fields_bash=required_fields,
|
|
121
|
+
required_fields_markdown=required_fields,
|
|
122
|
+
required_fields_css=required_fields,
|
|
123
|
+
enforce_atemporal=config_dict.get("enforce_atemporal", True),
|
|
124
|
+
ignore=config_dict.get("ignore", defaults.ignore),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Dict format: language-specific fields
|
|
60
128
|
return cls(
|
|
61
|
-
required_fields_python=
|
|
62
|
-
|
|
129
|
+
required_fields_python=required_fields.get("python", defaults.required_fields_python),
|
|
130
|
+
required_fields_typescript=required_fields.get(
|
|
131
|
+
"typescript", defaults.required_fields_typescript
|
|
132
|
+
),
|
|
133
|
+
required_fields_bash=required_fields.get("bash", defaults.required_fields_bash),
|
|
134
|
+
required_fields_markdown=required_fields.get(
|
|
135
|
+
"markdown", defaults.required_fields_markdown
|
|
63
136
|
),
|
|
137
|
+
required_fields_css=required_fields.get("css", defaults.required_fields_css),
|
|
64
138
|
enforce_atemporal=config_dict.get("enforce_atemporal", True),
|
|
65
|
-
ignore=config_dict.get("ignore",
|
|
139
|
+
ignore=config_dict.get("ignore", defaults.ignore),
|
|
66
140
|
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: CSS block comment header extraction and parsing
|
|
3
|
+
|
|
4
|
+
Scope: CSS and SCSS file header parsing
|
|
5
|
+
|
|
6
|
+
Overview: Extracts JSDoc-style block comments (/** ... */) from CSS and SCSS files.
|
|
7
|
+
Handles @charset declarations by allowing them before the header comment.
|
|
8
|
+
Parses structured header fields from comment content and cleans formatting
|
|
9
|
+
characters. Requires JSDoc-style comment (/**) not regular block comment (/*).
|
|
10
|
+
Processes multi-line comments and removes leading asterisks from content.
|
|
11
|
+
|
|
12
|
+
Dependencies: re module for regex pattern matching, base_parser.BaseHeaderParser for field parsing
|
|
13
|
+
|
|
14
|
+
Exports: CssHeaderParser class
|
|
15
|
+
|
|
16
|
+
Interfaces: extract_header(code) -> str | None for JSDoc comment extraction, parse_fields(header) inherited from base
|
|
17
|
+
|
|
18
|
+
Implementation: Regex-based JSDoc comment extraction with content cleaning and formatting removal
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
|
|
23
|
+
from src.linters.file_header.base_parser import BaseHeaderParser
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CssHeaderParser(BaseHeaderParser):
|
|
27
|
+
"""Extracts and parses CSS file headers from block comments."""
|
|
28
|
+
|
|
29
|
+
# Pattern to match JSDoc-style comment, allowing @charset before
|
|
30
|
+
JSDOC_PATTERN = re.compile(r'^(?:@charset\s+"[^"]+"\s*;\s*)?\s*/\*\*\s*(.*?)\s*\*/', re.DOTALL)
|
|
31
|
+
|
|
32
|
+
def extract_header(self, code: str) -> str | None:
|
|
33
|
+
"""Extract JSDoc-style comment from CSS code.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
code: CSS/SCSS source code
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Comment content or None if not found
|
|
40
|
+
"""
|
|
41
|
+
if not code or not code.strip():
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
match = self.JSDOC_PATTERN.match(code)
|
|
45
|
+
if not match:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Extract and clean the content
|
|
49
|
+
comment_content = match.group(1)
|
|
50
|
+
return self._clean_comment_content(comment_content)
|
|
51
|
+
|
|
52
|
+
def _clean_comment_content(self, content: str) -> str:
|
|
53
|
+
"""Remove comment formatting (leading asterisks) from content.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
content: Raw comment content
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Cleaned content without leading asterisks
|
|
60
|
+
"""
|
|
61
|
+
lines = content.split("\n")
|
|
62
|
+
cleaned_lines = []
|
|
63
|
+
|
|
64
|
+
for line in lines:
|
|
65
|
+
stripped = line.strip()
|
|
66
|
+
if stripped.startswith("*"):
|
|
67
|
+
stripped = stripped[1:].strip()
|
|
68
|
+
cleaned_lines.append(stripped)
|
|
69
|
+
|
|
70
|
+
return "\n".join(cleaned_lines)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"""
|
|
2
|
-
File: src/linters/file_header/field_validator.py
|
|
3
2
|
Purpose: Validates mandatory fields in file headers
|
|
4
|
-
Exports: FieldValidator class
|
|
5
|
-
Depends: FileHeaderConfig for field requirements
|
|
6
|
-
Implements: Configuration-driven validation with field presence checking
|
|
7
|
-
Related: linter.py for validator usage, config.py for configuration
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
Scope: File header field validation for all supported languages
|
|
5
|
+
|
|
6
|
+
Overview: Validates presence and quality of mandatory header fields. Checks that all
|
|
11
7
|
required fields are present, non-empty, and meet minimum content requirements.
|
|
12
|
-
Supports language-specific required fields and provides detailed violation messages
|
|
8
|
+
Supports language-specific required fields and provides detailed violation messages
|
|
9
|
+
for missing or empty fields. Uses configuration-driven validation to support
|
|
10
|
+
different field requirements per language type.
|
|
11
|
+
|
|
12
|
+
Dependencies: FileHeaderConfig for language-specific field requirements
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Exports: FieldValidator class
|
|
15
|
+
|
|
16
|
+
Interfaces: validate_fields(fields, language) -> list[tuple[str, str]] returns field violations
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Implementation: Configuration-driven validation with field presence and emptiness checking
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from .config import FileHeaderConfig
|
|
@@ -32,9 +32,7 @@ class FieldValidator:
|
|
|
32
32
|
"""
|
|
33
33
|
self.config = config
|
|
34
34
|
|
|
35
|
-
def validate_fields(
|
|
36
|
-
self, fields: dict[str, str], language: str
|
|
37
|
-
) -> list[tuple[str, str]]:
|
|
35
|
+
def validate_fields(self, fields: dict[str, str], language: str) -> list[tuple[str, str]]:
|
|
38
36
|
"""Validate all required fields are present.
|
|
39
37
|
|
|
40
38
|
Args:
|
|
@@ -44,26 +42,31 @@ class FieldValidator:
|
|
|
44
42
|
Returns:
|
|
45
43
|
List of (field_name, error_message) tuples for missing/invalid fields
|
|
46
44
|
"""
|
|
47
|
-
violations = []
|
|
48
45
|
required_fields = self._get_required_fields(language)
|
|
46
|
+
return [
|
|
47
|
+
error
|
|
48
|
+
for field_name in required_fields
|
|
49
|
+
if (error := self._check_field(fields, field_name))
|
|
50
|
+
]
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
violations.append((field_name, f"Empty mandatory field: {field_name}"))
|
|
52
|
+
def _check_field(self, fields: dict[str, str], field_name: str) -> tuple[str, str] | None:
|
|
53
|
+
"""Check a single field for presence and content."""
|
|
54
|
+
if field_name not in fields:
|
|
55
|
+
return (field_name, f"Missing mandatory field: {field_name}")
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
if not fields[field_name] or not fields[field_name].strip():
|
|
58
|
+
return (field_name, f"Empty mandatory field: {field_name}")
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
"""Get required fields for language.
|
|
60
|
+
return None
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
def _get_required_fields(self, language: str) -> list[str]:
|
|
63
|
+
"""Get required fields for language using dictionary lookup."""
|
|
64
|
+
language_fields = {
|
|
65
|
+
"python": self.config.required_fields_python,
|
|
66
|
+
"typescript": self.config.required_fields_typescript,
|
|
67
|
+
"javascript": self.config.required_fields_typescript,
|
|
68
|
+
"bash": self.config.required_fields_bash,
|
|
69
|
+
"markdown": self.config.required_fields_markdown,
|
|
70
|
+
"css": self.config.required_fields_css,
|
|
71
|
+
}
|
|
72
|
+
return language_fields.get(language, [])
|