GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
53 / 53
100.0%
Branches:
27 / 32
84.4%
Functions:
10 / 10
100.0%

linter/rules/naming-convention-rule.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Std imports
4 import "std/text/analysis";
5 import "std/data/vector";
6
7 // Own imports
8 import "@/ast/ast-nodes";
9 import "@/linter/lint-finding";
10
11 // Flags Spice-source identifiers that violate the naming conventions documented in STYLE_GUIDE.md
12 // (functions/procedures/methods: camelCase; structs/interfaces/unions: PascalCase; globals: SNAKE_CASE).
13 //
14 // This is deliberately a style rule, not a semantic one: unlike unused-symbol or shadowing checks (which stay
15 // compiler warnings), naming conventions are opinionated and belong to the opt-in `spice lint` command rather
16 // than every `build`/`run`/`test` invocation.
17 const string NAMING_CONVENTION_RULE_ID = "naming-convention";
18
19 /**
20 * Get the first char of a non-empty string
21 */
22 14 f<char> firstChar(const String& input) {
23 14 return input[0];
24 }
25
26 /**
27 * Check whether an identifier is written in camelCase (starts lowercase, no underscores)
28 */
29 8 f<bool> isCamelCase(const String& input) {
30
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 return !input.isEmpty() && isLower(firstChar(input)) && !input.contains("_");
31 }
32
33 /**
34 * Check whether an identifier is written in PascalCase (starts uppercase, no underscores)
35 */
36 6 f<bool> isPascalCase(const String& input) {
37
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 return !input.isEmpty() && isUpper(firstChar(input)) && !input.contains("_");
38 }
39
40 /**
41 * Check whether an identifier is written in SNAKE_CASE (all uppercase, underscores)
42 */
43 2 f<bool> isSnakeCase(const String& input) {
44
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if input.isEmpty() { return false; }
45 2 const unsigned long length = input.getLength();
46
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 time.
14 for unsigned long i = 0l; i < length; i++ {
47 13 const char c = input[i];
48
4/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 12 times.
13 if !isUpper(c) && c != '_' {
49 1 return false;
50 }
51 12 }
52 1 return true;
53 }
54
55 7 public p checkNamingFctDef(ASTFctDefNode* node, Vector<LintFinding>& findings) {
56 // Operator overloads (e.g. "op.plus") intentionally do not follow camelCase
57
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6 times.
7 if node.node.name.name.startsWith(OP_FCT_PREFIX) { return; }
58
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if !isCamelCase(node.node.name.name) {
59 1 const String message = String("Function name '") + node.node.name.name + "' should be camelCase";
60 1 findings.pushBack(LintFinding(node.node.name.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
61 1 }
62 6 }
63
64 3 public p checkNamingProcDef(ASTProcDefNode* node, Vector<LintFinding>& findings) {
65 // Constructors are always named "ctor" by language convention
66
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if node.isCtor { return; }
67
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if !isCamelCase(node.node.name.name) {
68 1 const String message = String("Procedure name '") + node.node.name.name + "' should be camelCase";
69 1 findings.pushBack(LintFinding(node.node.name.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
70 1 }
71 2 }
72
73 2 public p checkNamingStructDef(ASTStructDefNode* node, Vector<LintFinding>& findings) {
74
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if !isPascalCase(node.structName) {
75 1 const String message = String("Struct name '") + node.structName + "' should be PascalCase";
76 1 findings.pushBack(LintFinding(node.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
77 1 }
78 2 }
79
80 2 public p checkNamingInterfaceDef(ASTInterfaceDefNode* node, Vector<LintFinding>& findings) {
81
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if !isPascalCase(node.interfaceName) {
82 1 const String message = String("Interface name '") + node.interfaceName + "' should be PascalCase";
83 1 findings.pushBack(LintFinding(node.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
84 1 }
85 2 }
86
87 2 public p checkNamingUnionDef(ASTUnionDefNode* node, Vector<LintFinding>& findings) {
88
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if !isPascalCase(node.unionName) {
89 1 const String message = String("Union name '") + node.unionName + "' should be PascalCase";
90 1 findings.pushBack(LintFinding(node.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
91 1 }
92 2 }
93
94 2 public p checkNamingGlobalVarDef(ASTGlobalVarDefNode* node, Vector<LintFinding>& findings) {
95
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if !isSnakeCase(node.varName) {
96 1 const String message = String("Global variable name '") + node.varName + "' should be SNAKE_CASE";
97 1 findings.pushBack(LintFinding(node.codeLoc, NAMING_CONVENTION_RULE_ID, LintSeverity::WARNING, message));
98 1 }
99 2 }
100