src/linter/rules/NamingConventionRule.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | #include "NamingConventionRule.h" | ||
| 4 | |||
| 5 | #include <cctype> | ||
| 6 | |||
| 7 | #include <ast/ASTNodes.h> | ||
| 8 | |||
| 9 | namespace spice::compiler { | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Check whether an identifier is written in camelCase (starts lowercase, no underscores) | ||
| 13 | * | ||
| 14 | * @param input Identifier to check | ||
| 15 | * @return True if the identifier is camelCase | ||
| 16 | */ | ||
| 17 | 6 | static bool isCamelCase(const std::string &input) { | |
| 18 |
4/6✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 1 time.
|
6 | return !input.empty() && std::islower(static_cast<unsigned char>(input.front())) != 0 && !input.contains('_'); |
| 19 | } | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Check whether an identifier is written in PascalCase (starts uppercase, no underscores) | ||
| 23 | * | ||
| 24 | * @param input Identifier to check | ||
| 25 | * @return True if the identifier is PascalCase | ||
| 26 | */ | ||
| 27 | 2 | static bool isPascalCase(const std::string &input) { | |
| 28 |
4/6✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
|
2 | return !input.empty() && std::isupper(static_cast<unsigned char>(input.front())) != 0 && !input.contains('_'); |
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Check whether an identifier is written in SNAKE_CASE (all uppercase, underscores) | ||
| 33 | * | ||
| 34 | * @param input Identifier to check | ||
| 35 | * @return True if the identifier is SNAKE_CASE | ||
| 36 | */ | ||
| 37 | 2 | static bool isSnakeCase(const std::string &input) { | |
| 38 |
6/7✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 11 times.
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 1 time.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1 time.
|
15 | return !input.empty() && std::ranges::all_of(input, [](unsigned char c) { return std::isupper(c) || c == '_'; }); |
| 39 | } | ||
| 40 | |||
| 41 | 7 | void NamingConventionRule::checkFctDef(FctDefNode *node, std::vector<LintFinding> &findings) { | |
| 42 | // Operator overloads (e.g. "op.plus") intentionally do not follow camelCase | ||
| 43 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 6 times.
|
7 | if (node->name->isOperatorOverload()) |
| 44 | 1 | return; | |
| 45 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 14 taken 5 times.
|
6 | if (!isCamelCase(node->name->name)) |
| 46 |
1/2✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 15 not taken.
|
1 | findings.emplace_back(node->name->codeLoc, id(), LintSeverity::WARNING, |
| 47 |
2/4✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 21 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 19 not taken.
|
2 | "Function name '" + node->name->name + "' should be camelCase"); |
| 48 | } | ||
| 49 | |||
| 50 | 1 | void NamingConventionRule::checkProcDef(ProcDefNode *node, std::vector<LintFinding> &findings) { | |
| 51 | // Constructors are always named "ctor" by language convention | ||
| 52 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
|
1 | if (node->isCtor) |
| 53 | 1 | return; | |
| 54 | ✗ | if (!isCamelCase(node->name->name)) | |
| 55 | ✗ | findings.emplace_back(node->name->codeLoc, id(), LintSeverity::WARNING, | |
| 56 | ✗ | "Procedure name '" + node->name->name + "' should be camelCase"); | |
| 57 | } | ||
| 58 | |||
| 59 | 2 | void NamingConventionRule::checkStructDef(StructDefNode *node, std::vector<LintFinding> &findings) { | |
| 60 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 1 time.
|
2 | if (!isPascalCase(node->structName)) |
| 61 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 12 not taken.
|
1 | findings.emplace_back(node->codeLoc, id(), LintSeverity::WARNING, |
| 62 |
2/4✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 18 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 16 not taken.
|
2 | "Struct name '" + node->structName + "' should be PascalCase"); |
| 63 | 2 | } | |
| 64 | |||
| 65 | ✗ | void NamingConventionRule::checkInterfaceDef(InterfaceDefNode *node, std::vector<LintFinding> &findings) { | |
| 66 | ✗ | if (!isPascalCase(node->interfaceName)) | |
| 67 | ✗ | findings.emplace_back(node->codeLoc, id(), LintSeverity::WARNING, | |
| 68 | ✗ | "Interface name '" + node->interfaceName + "' should be PascalCase"); | |
| 69 | ✗ | } | |
| 70 | |||
| 71 | 2 | void NamingConventionRule::checkGlobalVarDef(GlobalVarDefNode *node, std::vector<LintFinding> &findings) { | |
| 72 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 1 time.
|
2 | if (!isSnakeCase(node->varName)) |
| 73 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 12 not taken.
|
1 | findings.emplace_back(node->codeLoc, id(), LintSeverity::WARNING, |
| 74 |
2/4✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 18 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 16 not taken.
|
2 | "Global variable name '" + node->varName + "' should be SNAKE_CASE"); |
| 75 | 2 | } | |
| 76 | |||
| 77 | } // namespace spice::compiler | ||
| 78 |