Option C Tools
Loading...
Searching...
No Matches
oct.linter.oct_lint Namespace Reference

Classes

class  LinterContext

Functions

bool _strict_config_requested ()
list[str] _validate_octrc_schema (dict cfg)
None log (str message, LinterContext|None ctx=None)
None print_header (str title, LinterContext|None ctx=None)
bool report (bool result, str message, bool fix_mode=False, LinterContext|None ctx=None)
bool is_excluded_dir (Path path, set exclude_dirs, list wildcard_exclude_dirs)
str read_file (Path path, LinterContext|None ctx=None)
list[Path] _git_changed_files (Path root)
 find_python_files (Path root, set|None exclude_dirs=None, list|None wildcard_exclude_dirs=None)
str expected_path_header (Path path, LinterContext ctx)
 validate_header_block (list[str] lines, Path path, LinterContext ctx)
int _header_boundary (list[str] lines, Path path, LinterContext ctx)
bool fix_header_block (Path path, str text, LinterContext ctx)
str preview_header_fix (Path path, LinterContext ctx)
 check_docstring (str text)
 check_dependencies_section (str text)
str|None _extract_module_docstring (str text)
 check_diagnostics_profile (str text)
 check_dbg_usage (str text, ast.Module|None tree=None)
 check_dbg_import (str text, ast.Module|None tree=None)
 _walk_function_body (ast.FunctionDef|ast.AsyncFunctionDef func_node)
 _direct_body_nodes (func_node)
list[tuple[str, int]] find_func_pattern_violations (str text, ast.Module|None tree=None)
 check_func_pattern (str text, ast.Module|None tree=None)
 check_syntax_warnings (str text, list[str]|None warnings_list=None)
 check_type_hints (str text, ast.Module|None tree=None, bool compact_mode=False)
bool _is_security_sensitive_name (str name)
tuple[bool, str] check_dbg_assert_safety (str text, ast.Module|None tree=None, str filename="")
dict[str, set[str]] build_test_index (LinterContext ctx)
 check_tests_exist (Path module_path, LinterContext ctx)
None _rotate_lint_logs (Path log_dir, int max_files=_MAX_LINT_LOG_FILES)
dict _lint_single_file (Path path, LinterContext ctx, dict active_rules, bool fix_mode, bool dry_run=False)
int run_linter (Path project_root, list[str]|None argv=None)
None main ()

Variables

 _OCT_PACKAGE_DIR = Path(__file__).resolve().parent.parent
dict _DEFAULT_RULES
dict _PROFILES
str SHEBANG = "#!/usr/bin/env python3"
str ENCODING = "# -*- coding: utf-8 -*-"
 _DEP_SECTION_RE
 _DEP_LINE_RE = re.compile(r'^\s*-\s+\S+\s*\‍([^)]+\‍)\s*$', re.MULTILINE)
 _DEP_BULLET_RE = re.compile(r'^\s*[-*]\s+\S', re.MULTILINE)
 _SECURITY_SENSITIVE_KEYWORDS
 _IMPORT_STMT_RE

Detailed Description

Purpose
-------
Provide a strict, deterministic linter for Option C projects that validates
mandatory header blocks, module docstrings, diagnostics profiles, and basic
testing presence, and can optionally auto-fix header blocks.

Responsibilities
----------------
- Walk the project tree from a given Option C project root or directory or single file given as an argument.
- Skip excluded directories and the linter's own files.
- Validate the 4-line mandatory header block for every Python module.
- Validate the presence and structure of the module docstring.
- Validate the presence of a Diagnostics profile block (Domain + L2/L3/L4).
- Validate basic ``_dbg`` usage in non-trivial modules; ``_dbg`` is the
  structured debug logger provided by the ``oc_diagnostics`` package
  (``from oc_diagnostics import _dbg``).
- Validate the presence of regression tests for each module.
- Optionally auto-fix header blocks while archiving originals.
- Produce a detailed log file and a colorized terminal summary.
- Optionally emit machine-readable JSON to stdout (``--json``).

Diagnostics
-----------
Domain: OCT-LINTER
Levels:
    L2 — lifecycle
    L3 — semantic details
    L4 — deep tracing

Contracts
---------
- Must not modify files unless ``--fix-headers`` is explicitly requested.
- Must treat the provided project root as authoritative for all relative paths.
- Must write a deterministic log file into the project's ``logs/`` directory
  when writable; warns and continues if the log directory is unwritable.
- Must not import project modules; analysis is purely static.
- Must be callable both from the OCT CLI and as a standalone module.

Dependencies
------------
- oct.core.exclusions (directory and wildcard exclusion lists)
- oct.core.parsing (safe AST parsing helper)
- oct.core.paths (project-relative path helper)
- oct.core.waivers (inline waiver parsing)

Function Documentation

◆ _direct_body_nodes()

oct.linter.oct_lint._direct_body_nodes ( func_node)
protected
Yield AST nodes that are directly in this function's body (not in
nested function bodies).  Uses an iterative approach to avoid
descending into nested FunctionDef/AsyncFunctionDef.

Definition at line 852 of file oct_lint.py.

Here is the caller graph for this function:

◆ _extract_module_docstring()

str | None oct.linter.oct_lint._extract_module_docstring ( str text)
protected
Return the text of the first module-level triple-quoted string found
before the first ``def`` or ``class`` statement, or ``None`` if absent.

Scoping to pre-code text prevents false positives from in-function
string literals that happen to contain keywords like 'L2', 'Diagnostics',
or 'Domain:'.

Definition at line 747 of file oct_lint.py.

Here is the caller graph for this function:

◆ _git_changed_files()

list[Path] oct.linter.oct_lint._git_changed_files ( Path root)
protected
Return .py files modified in the git working tree (staged + unstaged).

Phase 4A (G-A7): thin shim over :func:`oct.core.git.git_changed_files`.
The core module enforces subprocess discipline, credential redaction,
and path-escape filtering; the linter's sole contract here is to
preserve the historical "never crash on any git failure" behaviour
by collapsing every ``GitError`` back to an empty list.

Definition at line 535 of file oct_lint.py.

Here is the caller graph for this function:

◆ _header_boundary()

int oct.linter.oct_lint._header_boundary ( list[str] lines,
Path path,
LinterContext ctx )
protected
Return the line index where file content begins, after any existing
header elements.  OI-509: thin wrapper that delegates to
:func:`oct.core.parsing.header_boundary` so linter and formatter share
one implementation.

Definition at line 630 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _is_security_sensitive_name()

bool oct.linter.oct_lint._is_security_sensitive_name ( str name)
protected

Definition at line 1059 of file oct_lint.py.

Here is the caller graph for this function:

◆ _lint_single_file()

dict oct.linter.oct_lint._lint_single_file ( Path path,
LinterContext ctx,
dict active_rules,
bool fix_mode,
bool dry_run = False )
protected
Lint one Python file and return a result dict with check outcomes.

Thread-safe: logging uses ctx.log_lock internally.

Definition at line 1214 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _rotate_lint_logs()

None oct.linter.oct_lint._rotate_lint_logs ( Path log_dir,
int max_files = _MAX_LINT_LOG_FILES )
protected
Remove old lint log files, keeping only the most recent max_files.

Definition at line 1203 of file oct_lint.py.

Here is the caller graph for this function:

◆ _strict_config_requested()

bool oct.linter.oct_lint._strict_config_requested ( )
protected
Return True when strict-config enforcement is active.

Strict mode is opt-in via the ``OCT_STRICT_CONFIG`` environment variable
(any truthy value: ``1``, ``true``, ``yes``, ``on``). The ``--strict-config``
CLI flag wired up in ``oct/cli.py`` sets this env var before dispatch.

Definition at line 158 of file oct_lint.py.

Here is the caller graph for this function:

◆ _validate_octrc_schema()

list[str] oct.linter.oct_lint._validate_octrc_schema ( dict cfg)
protected
Validate the structure of a loaded ``.octrc.json`` dict.

Returns a list of human-readable error strings. Empty list means the
config is structurally acceptable. Unknown keys are tolerated (forward
compat); only known keys are type-checked.

Definition at line 170 of file oct_lint.py.

Here is the caller graph for this function:

◆ _walk_function_body()

oct.linter.oct_lint._walk_function_body ( ast.FunctionDef | ast.AsyncFunctionDef func_node)
protected
Yield AST nodes within a function body, stopping at nested function
boundaries so that each function gets an independent check.

Definition at line 838 of file oct_lint.py.

◆ build_test_index()

dict[str, set[str]] oct.linter.oct_lint.build_test_index ( LinterContext ctx)
OI-510 / FS-516: one-shot scan of ``ctx.tests_dir`` producing two
lookup sets so :func:`check_tests_exist` becomes O(1) per module
instead of re-walking the tests tree for every file linted.

The index is cached on :attr:`LinterContext.test_index` — subsequent
calls return the cached value directly.

Returns a dict with:

* ``name_stems``     — stems ``<s>`` for which ``test_<s>.py`` exists.
* ``imported_stems`` — module stems referenced in any test file by
  a whole-word ``import`` / ``from`` statement.

Definition at line 1139 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_dbg_assert_safety()

tuple[bool, str] oct.linter.oct_lint.check_dbg_assert_safety ( str text,
ast.Module | None tree = None,
str filename = "" )
Flag ``_dbg_assert`` calls made inside security-sensitive contexts.

``_dbg_assert`` is a diagnostic aid — in production mode it is a no-op.
Per AP-16 it must never be used for safety invariants (authentication,
authorization, input validation, crypto, etc.). This check walks the AST
looking for calls named ``_dbg_assert`` (bare or attribute access) and
verifies that none of the enclosing functions, classes, or the module's
filename stem contain a security-sensitive keyword.

Definition at line 1064 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_dbg_import()

oct.linter.oct_lint.check_dbg_import ( str text,
ast.Module | None tree = None )
Verify that ``_dbg`` is imported from ``oc_diagnostics`` at module level.

The canonical import is ``from oc_diagnostics import _dbg``.  Files shorter
than 20 lines are exempt (same threshold as ``check_dbg_usage``).  Aliased
imports (``import _dbg as ...``) are not accepted — the Option C spec
requires the canonical form.

Definition at line 814 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_dbg_usage()

oct.linter.oct_lint.check_dbg_usage ( str text,
ast.Module | None tree = None )
Validate that non-trivial modules call ``_dbg()`` for diagnostics.

``_dbg`` is the structured debug logger from ``oc_diagnostics``
(``from oc_diagnostics import _dbg``).  Files shorter than 20 lines
are considered trivial scaffolding and are exempt from this check.

Uses AST parsing to detect actual ``_dbg()`` call nodes, avoiding
false positives from comments or string literals.

Definition at line 785 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_dependencies_section()

oct.linter.oct_lint.check_dependencies_section ( str text)
Validate the Dependencies section of a module docstring at strict profile.

Spec reference: Appendix C — Dependencies docstring format. Each entry
must follow ``- module_name (purpose description)`` so ``oct deps`` can
parse the dependency graph.

Returns ``(ok, msg)``. Empty Dependencies sections are accepted (a module
may have no project-internal dependencies); malformed entries are rejected.

Definition at line 715 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_diagnostics_profile()

oct.linter.oct_lint.check_diagnostics_profile ( str text)
Validate that a Diagnostics profile is present in the module docstring.

Scopes the search to the module docstring (text before the first
``def``/``class``) to avoid false positives from in-code string literals
that happen to contain 'L2', 'L3', 'L4', 'Diagnostics', or 'Domain:'.
Falls back to the full file if no module docstring is found.

Definition at line 765 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_docstring()

oct.linter.oct_lint.check_docstring ( str text)
Validate that a module docstring exists and contains the required
Option C sections.

Definition at line 691 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_func_pattern()

oct.linter.oct_lint.check_func_pattern ( str text,
ast.Module | None tree = None )
Validate that every function body containing ``_dbg()`` first defines
``func = "function_name"``.

Uses ``ast.parse()`` to build a syntax tree, then walks each function's
body (stopping at nested function boundaries) to check for the pattern.
Exempt trivial files (< 20 lines) — same threshold as check_dbg_usage.

Delegates to :func:`find_func_pattern_violations` for the actual
detection; this wrapper converts the result into a ``(bool, message)``
pair for the linter pipeline.

Definition at line 936 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_syntax_warnings()

oct.linter.oct_lint.check_syntax_warnings ( str text,
list[str] | None warnings_list = None )
Check for Python syntax warnings (invalid escape sequences etc.).

Python 3.12+ emits ``SyntaxWarning`` for invalid escape sequences in
string literals.  These will become ``SyntaxError`` in a future Python
version, so they should be surfaced as lint findings.

Definition at line 977 of file oct_lint.py.

Here is the caller graph for this function:

◆ check_tests_exist()

oct.linter.oct_lint.check_tests_exist ( Path module_path,
LinterContext ctx )
Validate that at least one test file references the module either by
naming convention (``test_<module>.py``) or by an explicit import
statement (``import <module>`` or ``from <module>``).

Two-stage check, now O(1) per call: consults the index built once
per :func:`run_linter` invocation by :func:`build_test_index`.

Definition at line 1179 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_type_hints()

oct.linter.oct_lint.check_type_hints ( str text,
ast.Module | None tree = None,
bool compact_mode = False )
Validate that public functions have type annotations on all parameters
and a return annotation.

Private functions (leading underscore) and dunder methods are always
exempt — they are internal implementation details (OI-417). The
``compact_mode`` parameter is retained for API compatibility but no
longer changes behaviour.

Trivial files (< 20 lines) are exempt.

Definition at line 992 of file oct_lint.py.

Here is the caller graph for this function:

◆ expected_path_header()

str oct.linter.oct_lint.expected_path_header ( Path path,
LinterContext ctx )
Compute the expected file identity header for a given module path.

Definition at line 593 of file oct_lint.py.

Here is the caller graph for this function:

◆ find_func_pattern_violations()

list[tuple[str, int]] oct.linter.oct_lint.find_func_pattern_violations ( str text,
ast.Module | None tree = None )
Return ``[(func_name, lineno), ...]`` for every function that calls
``_dbg()`` without a prior ``func = "..."`` assignment.

Returns an empty list for trivial files (< 20 lines) or files with
syntax errors.  This is the single source of truth for func-pattern
detection — used by :func:`check_func_pattern` (linter pass/fail)
and by :func:`oct.formatter.oct_format.fix_func_pattern` (auto-fix).

Definition at line 868 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ find_python_files()

oct.linter.oct_lint.find_python_files ( Path root,
set | None exclude_dirs = None,
list | None wildcard_exclude_dirs = None )
Yield all Python files under root, excluding the linter itself and
excluded directories.

Parameters
----------
root:
    The project root to scan.
exclude_dirs:
    Set of directory names to exclude.  Defaults to the module-level
    ``EXCLUDE_DIRS`` constant.
wildcard_exclude_dirs:
    List of substrings; directories whose name contains any of these
    are excluded.  Defaults to ``WILDCARD_EXCLUDE_DIRS``.

Definition at line 551 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fix_header_block()

bool oct.linter.oct_lint.fix_header_block ( Path path,
str text,
LinterContext ctx )
Rewrite the top of the file to enforce the Option C 4-line header.
The original file is archived in a `.linter_archive` directory.

Definition at line 643 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_excluded_dir()

bool oct.linter.oct_lint.is_excluded_dir ( Path path,
set exclude_dirs,
list wildcard_exclude_dirs )
Return True if the directory name should be excluded from scanning.

Definition at line 510 of file oct_lint.py.

Here is the caller graph for this function:

◆ log()

None oct.linter.oct_lint.log ( str message,
LinterContext | None ctx = None )
Write a single line to the linter log file.

OI-513: ``ctx`` is required in practice — every internal callsite
threads it through. The ``None`` branch is kept as a silent no-op so
external callers that import ``log()`` standalone do not crash.

Definition at line 468 of file oct_lint.py.

Here is the caller graph for this function:

◆ main()

None oct.linter.oct_lint.main ( )
Standalone entry point when running this module directly.

Definition at line 2065 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ preview_header_fix()

str oct.linter.oct_lint.preview_header_fix ( Path path,
LinterContext ctx )
Compute what fix_header_block would produce, without writing.
Returns the corrected 4-line header block as a string.

Definition at line 672 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ print_header()

None oct.linter.oct_lint.print_header ( str title,
LinterContext | None ctx = None )
Write a section header to the log.

Definition at line 482 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ read_file()

str oct.linter.oct_lint.read_file ( Path path,
LinterContext | None ctx = None )
Read a file as UTF-8, returning an empty string on failure.

Definition at line 523 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ report()

bool oct.linter.oct_lint.report ( bool result,
str message,
bool fix_mode = False,
LinterContext | None ctx = None )
Log a PASS/FAIL (or FIXED) line and return the result.

Definition at line 492 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ run_linter()

int oct.linter.oct_lint.run_linter ( Path project_root,
list[str] | None argv = None )
Run the Option C linter on the given project root.

Parameters
----------
project_root:
    The detected Option C project root directory.
argv:
    Optional list of command-line arguments (e.g. ["--fix-headers"]).
    If None, ``argparse`` will use ``sys.argv``.

Returns
-------
int
    Exit code: 0 if all checks passed, 1 if any failures detected.

Definition at line 1530 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ validate_header_block()

oct.linter.oct_lint.validate_header_block ( list[str] lines,
Path path,
LinterContext ctx )
Validate the mandatory 4-line header block.

Returns (ok, errors) where:
- ok = True if header block is correct
- errors = list of error messages

Definition at line 601 of file oct_lint.py.

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ _DEFAULT_RULES

dict oct.linter.oct_lint._DEFAULT_RULES
protected
Initial value:
1= {
2 "header": True,
3 "docstring": True,
4 "diagnostics_profile": True,
5 "dbg_usage": True,
6 "dbg_import": True,
7 "func_pattern": True,
8 "tests_exist": True,
9 "type_hints": True,
10 "no_hardcoded_secrets": True,
11 "dbg_assert_safety": True,
12 "dependencies_section": True,
13}

Definition at line 91 of file oct_lint.py.

◆ _DEP_BULLET_RE

oct.linter.oct_lint._DEP_BULLET_RE = re.compile(r'^\s*[-*]\s+\S', re.MULTILINE)
protected

Definition at line 712 of file oct_lint.py.

◆ _DEP_LINE_RE

oct.linter.oct_lint._DEP_LINE_RE = re.compile(r'^\s*-\s+\S+\s*\‍([^)]+\‍)\s*$', re.MULTILINE)
protected

Definition at line 711 of file oct_lint.py.

◆ _DEP_SECTION_RE

oct.linter.oct_lint._DEP_SECTION_RE
protected
Initial value:
1= re.compile(
2 r'Dependencies\s*\n\s*[-]+\s*\n(.*?)(?:\n\s*\n|\Z)',
3 re.DOTALL,
4)

Definition at line 707 of file oct_lint.py.

◆ _IMPORT_STMT_RE

oct.linter.oct_lint._IMPORT_STMT_RE
protected
Initial value:
1= re.compile(
2 r'(?:^|\s)(?:import|from)\s+([a-zA-Z_][a-zA-Z0-9_]*)',
3 re.MULTILINE,
4)

Definition at line 1133 of file oct_lint.py.

◆ _OCT_PACKAGE_DIR

oct.linter.oct_lint._OCT_PACKAGE_DIR = Path(__file__).resolve().parent.parent
protected

Definition at line 75 of file oct_lint.py.

◆ _PROFILES

dict oct.linter.oct_lint._PROFILES
protected

Definition at line 105 of file oct_lint.py.

◆ _SECURITY_SENSITIVE_KEYWORDS

oct.linter.oct_lint._SECURITY_SENSITIVE_KEYWORDS
protected
Initial value:
1= frozenset({
2 "auth", "authenticate", "authorize", "permission", "security",
3 "validate", "verify", "credential", "login", "session", "sanitize",
4 "encrypt", "decrypt", "password", "token",
5})

Definition at line 1052 of file oct_lint.py.

◆ ENCODING

str oct.linter.oct_lint.ENCODING = "# -*- coding: utf-8 -*-"

Definition at line 412 of file oct_lint.py.

◆ SHEBANG

str oct.linter.oct_lint.SHEBANG = "#!/usr/bin/env python3"

Definition at line 411 of file oct_lint.py.