CodeLingo vs Linters: TLDR
Tenet’s are 10x shorter and faster to write than Linters
Comparison of size in code
Lines (CL) | Lines (L) | Words (CL) | Words (L) | Bytes (CL) | Bytes (L) | Byte % (CL/L) | Tenet name (CL) | linter name (L) |
---|---|---|---|---|---|---|---|---|
18 | 681 | 49 | 2084 | 524 | 15616 | 3.36% | unconvert | unconvert |
19 | 110 | 64 | 275 | 580 | 2198 | 26.39% | init | gochecknoinits |
18 | 136 | 67 | 353 | 623 | 2307 | 27.00% | bool-param | nofuncflags |
18 | 137 | 63 | 310 | 512 | 2168 | 23.62% | todo | godox |
42 | 753 | 115 | 2387 | 1154 | 20307 | 5.68% | tested | blanket |
- CL
- CodeLingo
- L
- Linter

Making tenets with CLQL that do the job of existing linters
I started learning Go and CodeLingo / CLQL at the same time, so while I have found it generally easy and straightforward to create these tenets, I feel like I could’ve knocked them out even faster if I didn’t need to look up answers to questions such as ‘what is an interface in golang?’, for example. The process has been intuitive; I think in part to having a good naming convention. I’ve had to learn what CodeLingo is, what a ‘tenet’ and a ‘flow’ is. Long story short, they are just as what they sound like they are.
Defintions taken from the project docs
- tenet
- A Tenet is an encoded project-specific best practice used to guide development.
(an encoded best practice)
- flow
- A Flow is an automated development workflow that leverages Tenets to do some task, for example automating code reviews.
(an automated workflow)
The general process of writing one tenet
Writing the CLQL
What is simple to implement in CodeLingo may be difficult for 3rd party linters, however, as this write-up should demonstrate.
Writing a tenet to describe an antipattern might be trivial in CLQL, but the corresponding linter may be orders of magnitude more complex.
The first step is usually to generate a verbose CLQL query from the CodeLingo playground.
To do this you select the part of the code you would like to match and then click the generate1 button.
The next step is to refine that query into something that captures the logic of what you want a tenet to match.
Typically, the generated query comprises the bulk of the code you end up with. You can therefore choose what to leave in, or add new logic, depending on the needs of the tenet.
Before: Generated CLQL
This generated query contains a greater number of facts than we really need, so we can remove some of them.
|
|
Refining the CLQL
The process of crafting CLQL is mainly subtractive, especially for simple tenets.
@@ -2,13 +2,10 @@ import codelingo/ast/go go.file(depth = any): go.decls: - @playground.highlight go.func_decl: + @review comment go.ident: - child_count == 0 name == "init" - private == "true" - public == "false" go.func_type: go.field_list: child_count == 0
After: Refined CLQL
Short and sweet.
|
|
See the full article here for further explanation.
Results
Comparison of size Unit tests
Lines (CL) | Lines (L) | Words (CL) | Words (L) | Bytes (CL) | Bytes (L) | Byte % (CL/L) | Tenet name (CL) | linter name (L) |
---|---|---|---|---|---|---|---|---|
13 | N/A2 | 25 | N/A2 | 246 | N/A2 | N/A2 | unconvert | unconvert |
32 | 201 | 64 | 407 | 333 | 3167 | 10.51% | init | gochecknoinits |
16 | 24 | 27 | 36 | 166 | 261 | 63.60% | bool-param | nofuncflags |
29 | 130 | 80 | 407 | 440 | 3000 | 14.67% | todo | godox |
14 | 124 | 25 | 185 | 156 | 1229 | 12.69% | tested | blanket |
- N/A
- The original linter did not contain unit tests.
Links to source code
tenet name | linter name | tenet code | forge | linter code | description |
---|---|---|---|---|---|
init | gochecknoinits | codelingo.yaml | GitHub | leighmcculloch/gochecknoinits | Check that no init functions are present in Go code. |
unconvert | unconvert | codelingo.yaml | GitHub | mdempsky/unconvert | Remove unnecessary type conversions from Go source |
bool-param | nofuncflags | codelingo.yaml | GitHub | fsamin/nofuncflags | because flag arguments are ugly |
todo | godox | codelingo.yaml | GitHub | 766b/godox | extract speficic comments from Go code based on keywords |
tested3 | blanket3 | codelingo.yaml | GitLab | verygoodsoftwarenotvirus/blanket | a coverage helper tool |
The work it took to write these tenets
tenet name | time to write | min clicks | actual clicks4 | reason for generate query1 click/s | reason for time spent greater or less than 10 mins |
---|---|---|---|---|---|
init | 10 mins | 1 | 1 | to find an initial fact for a top-level init function | |
unconvert | 20 mins | 1 | 2 | to generalise unit test to any type conversion | to create string variable to match function name with ident type |
bool-param | 5 mins | 1 | 1 | to generate initial query | the generated query was ~= the finished tenet |
todo | 10 mins | 1 | 1 | to find the CLQL fact for comment | |
tested3 | 20 mins | 1 | 2 | to find the initial query for a filename with identifier | learning to use CLQL functions |
- min clicks
- The number of times I needed to press generate query1 to discover the CLQL syntax I needed.
- actual clicks
- The approximate number of times I ended up to pressing generate query, for exploratory purposes.
The work it took to write their unit tests
tenet name | time to write4 | generate1 clicks4 | additional time | reason for additional time | test runs5 | reason for additional testing of unit tests |
---|---|---|---|---|---|---|
init | 10 mins | 1 | 2 | |||
unconvert | 10 mins | 2 | 10 mins | to find example code for unit test | 2 | |
bool-param | 5 mins | 1 | 2 | |||
todo | 10 mins | 1 | 2 | |||
tested3 | 20 mins | 1 | 4 | ensure multi-file unit tests are working as expected |
A nice surprise
-
Blanket (the original linter) has a limitation. It can handle single-level selector expressions with great ease, but it doesn’t recursively dive into those selectors for a number of reasons.
The ‘tested’ tenet, however, can find if said method was called with arbitrary nesting.
It doesn’t actually look at the function call to make sure it is the same object that is calling. It only checks that the method being called has the same name and that it is being called within a test function and file of appropriate name.
So it doesn’t have the same recursive limitation as blanket, but may give rise to false positives in some circumstances.
Read the full article here.
If this article appears incomplete, it may be intentional. Try prompting for a continuation.