A command-line static analysis tool for Python source code, built from scratch — no pycodestyle, no flake8, no third-party parser. It walks source files line-by-line (and, for the deeper checks, through Python's own ast module) to flag common PEP 8–style issues and reports them in a file: Line N: <code> <message> format familiar from tools like pylint and flake8.
The repository is organized as five progressive implementations (level1 → level5), each one a complete, runnable analyzer that builds on the last — going from a single 18-line line-length checker to a 785-line analyzer with 12 rules, AST-based checks, and multi-file/directory scanning.
- Overview
- Rules Reference
- How It Works
- Development Stages
- Getting Started
- Example
- Design Notes & Known Limitations
- Roadmap
- Contributing
- License
staticCodeAnalyzer inspects .py files and prints one diagnostic line per violation found, in the form:
<file>: Line <line_number>: <rule_code> <message>
It supports two run modes (from Level 3 onward):
- Single file — pass a path to one
.pyfile. - Directory — pass a path to a folder; every
.pyfile inside is scanned and results are printed per file.
Each rule is implemented as its own custom Exception subclass, raised internally and immediately caught to print a formatted message — used as a lightweight, self-contained way to carry the line number, file path, and rule-specific context (e.g. the offending identifier) through to the point where it's printed.
The final stage (level5) implements all 12 checks below. Earlier stages implement a subset — see Development Stages.
| Code | Rule | What it flags |
|---|---|---|
S001 |
Line too long | Line exceeds 79 characters |
S002 |
Indentation | Indentation is not a multiple of four spaces |
S003 |
Unnecessary semicolon | A statement ends with a redundant ; |
S004 |
Inline comment spacing | Fewer than two spaces before an inline # comment |
S005 |
TODO found | A TODO marker (any case) appears in a comment |
S006 |
Blank lines | More than two consecutive blank lines precede a line of code |
S007 |
Spacious constructor | More than one space after class or def before the name |
S008 |
Class naming | Class name is not in CamelCase |
S009 |
Function naming | Function name is not in snake_case |
S010 |
Argument naming | A function argument name is not in snake_case |
S011 |
Variable naming | A local variable name is not in snake_case |
S012 |
Mutable default argument | A function default argument is a mutable object (e.g. []) |
The analyzer combines two complementary strategies depending on the check:
- Lexical / character-position scanning (
S001–S007) — for every line,indexFinder()locates the position of#, the first non-whitespace character, and any;, and each rule function reasons about those positions (e.g. "is the;before or after the comment marker?", "is the first character at a column divisible by 4?"). - AST-based structural analysis (
S008–S012, introduced in Level 5) — the file is additionally parsed with Python's built-inastmodule to reliably extract class names, function names, argument names, assignment targets, and default argument values, which are then checked against naming conventions or mutability rules.
Single-file mode raises and prints each violation as it's found; directory mode accumulates violations per file into a shared dictionary (fileErrorDict) keyed by filename and line number, then reports them in file/line order once every file has been scanned.
Each level is a fully working analyzer in its own right — useful for seeing how the tool's rule set and I/O model grew over time.
| Stage | File | Size | Rules implemented | Input mode |
|---|---|---|---|---|
| 1 | staticCodeAnalyzer - level1.py |
18 lines | S001 |
Single file, via input() |
| 2 | staticCodeAnalyzer - level2.py |
254 lines | S001–S006 |
Single file, via input() |
| 3 | staticCodeAnalyzer - level3.py |
421 lines | S001–S006 |
Single file or directory, via CLI arg |
| 4 | staticCodeAnalyzer - level4.py |
573 lines | S001–S009 |
Single file or directory, via CLI arg |
| 5 | staticCodeAnalyzer - level5.py |
785 lines | S001–S012 (adds AST-based checks) |
Single file or directory, via CLI arg |
If you just want the most capable version of the tool, use level5.
- Python 3.6+ (the code relies on f-strings)
- No third-party packages — only the standard library (
ast,os,string,sys)
git clone https://github.com/AstronauticalCodes/staticCodeAnalyzer.git
cd staticCodeAnalyzerThere's nothing to pip install — each level is a standalone script.
From Level 3 onward, the target path is passed as a command-line argument:
# Analyze a single file
python "staticCodeAnalyzer - level5.py" path/to/your_file.py
# Analyze every .py file in a directory
python "staticCodeAnalyzer - level5.py" path/to/your_project/Note on paths: the script currently prepends a hardcoded local development path (
halfPath, near the top of the file — originally set to a path on the author's own machine) to any input that doesn't start withC. To run it in your own environment, either:
- pass an absolute path starting with
C(Windows-style, e.g.C:\Users\you\project\file.py), which bypasses the prefix entirely, or- edit the
halfPathvariable in the script to point at your own working directory (or blank it out to use paths as-is).
Levels 1 and 2 instead prompt for the file path via input():
python "staticCodeAnalyzer - level1.py"
# then type or paste the file path when promptedGiven a file with a couple of style issues:
class bad_class_name:
def BadFunctionName(self, X):
y=1;
print(y) #no space before commentRunning the Level 5 analyzer against it would report something like:
example.py: Line 1: S008 Class name 'bad_class_name' should use CamelCase
example.py: Line 2: S009 Function name 'BadFunctionName' should use snake_case
example.py: Line 2: S010 Argument name 'X' should be written in snake_case
example.py: Line 3: S003 Unnecessary semicolon
example.py: Line 3: S011 Variable 'y' should be in snake_case
example.py: Line 4: S004 At least two spaces required before inline comments
Documented here for transparency and as a map for anyone picking up the Roadmap:
- Custom exceptions shadow built-ins.
IndentationErroris redefined as a custom exception class, shadowing Python's built-in exception of the same name within the module's scope. - Directory mode assumes Windows-style paths. File paths inside a scanned directory are joined with a literal
'\\', so directory mode as currently written targets Windows; single-file mode is platform-agnostic. - Character-index parsing instead of
tokenize. Comment/semicolon detection is done by scanning for literal#and;characters rather than using Python'stokenizemodule, so a#or;inside a string literal can produce a false positive. - Substring-based TODO detection.
S005checks for the literal substringtodo(in various letter-case combinations) anywhere after a#, so it can also match words that merely contain "todo" rather than a deliberate TODO marker. - No automated tests. Correctness has been validated manually rather than with a test suite.
Ideas for anyone extending the project:
- Replace manual character-index scanning with Python's
tokenizemodule for more robust comment/string handling - Use
pathlibandos.path.joinfor cross-platform directory scanning - Add a
pyproject.tomland console-script entry point (static-code-analyzer <path>instead of invoking the.pyfile directly) - Add a
pytestsuite covering each rule, plus a CI workflow to run it on push - Support a config file / CLI flags for ignoring specific rules or codes
- Add a machine-readable output mode (JSON) for editor and CI integration
- Consolidate the five levels into a single versioned entry point, keeping the stage history in git tags/branches instead of parallel files
Contributions are welcome:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-improvement) - Commit your changes with a clear message
- Open a pull request describing what changed and why
For anything nontrivial, opening an issue first to discuss the approach is appreciated.
No license file is currently included in this repository, which by default means all rights are reserved by the author. If you intend for others to reuse or modify this code, consider adding a LICENSE file (e.g. MIT or Apache-2.0).