Lab 02: Linting
| Duration | Level | Prerequisites |
|---|---|---|
| 45 min | Intermediate | Lab 01 |
Learning Objectives
- Run ESLint on TypeScript code and interpret lint results
- Run Ruff on Python code and understand rule categories
- Run .NET Analyzers on C# code
- Run Checkstyle on Java code
- Run golangci-lint on Go code
- Understand the SARIF output format for linter results
Prerequisites
- Completed Lab 01: Explore Demo Apps
- All scanning tools installed (ESLint, Ruff, golangci-lint)
Exercises
Exercise 1: Run ESLint on TypeScript (cq-demo-app-001)
Working Directory: Run the following commands from the
code-quality-scan-demo-apprepository root.
Navigate to the TypeScript demo app and install dependencies:
cd cq-demo-app-001
npm install
Run ESLint with the default stylish formatter to see human-readable output:
npx eslint src/ --format stylish
You should see violations such as:
prefer-const— variables declared withletthat are never reassigned@typescript-eslint/no-unused-vars— declared but unused variables@typescript-eslint/no-explicit-any— use ofanytype instead of specific types

Now run ESLint with SARIF output to see the machine-readable format:
npx eslint src/ --format @microsoft/eslint-formatter-sarif --output-file eslint-results.sarif
Note: If the SARIF formatter is not installed, install it first:
npm install -D @microsoft/eslint-formatter-sarif
Examine the SARIF output:
Get-Content eslint-results.sarif | ConvertFrom-Json | ConvertTo-Json -Depth 10 | Select-Object -First 60

cd ..
Exercise 2: Run Ruff on Python (cq-demo-app-002)
Navigate to the Python demo app and run Ruff:
cd cq-demo-app-002
ruff check src/
Ruff organizes rules by category prefix:
| Prefix | Category | Example |
|---|---|---|
F | Pyflakes — logic errors | F401 (unused import), F841 (unused variable) |
E | pycodestyle — style errors | E501 (line too long) |
W | pycodestyle — warnings | W291 (trailing whitespace) |
I | isort — import ordering | I001 (unsorted imports) |
N | pep8-naming — naming conventions | N802 (function name should be lowercase) |

Run Ruff with SARIF output:
ruff check src/ --output-format sarif --output-file ruff-results.sarif
cd ..
Exercise 3: Run .NET Analyzers on C# (cq-demo-app-003)
Navigate to the C# demo app and build with analyzers enabled:
cd cq-demo-app-003
dotnet build /p:TreatWarningsAsErrors=false -warnaserror- 2>&1 | Select-String "warning CA|error CA"
Common .NET Analyzer findings include:
| Rule | Description |
|---|---|
| CA1822 | Mark members as static |
| CA2007 | Consider calling ConfigureAwait |
| CA1062 | Validate arguments of public methods |
| CA1031 | Do not catch general exception types |
| IDE0060 | Remove unused parameter |

To generate SARIF output from the .NET build:
dotnet build /p:ErrorLog=dotnet-results.sarif,version=2.1
cd ..
Exercise 4: Run Checkstyle on Java (cq-demo-app-004)
Navigate to the Java demo app and run Checkstyle via Maven:
cd cq-demo-app-004
.\mvnw.cmd checkstyle:check 2>&1 | Select-Object -Last 30
Note on cross-platform: On Linux/macOS, use
./mvnwinstead of.\mvnw.cmd.
Common Checkstyle findings:
| Rule | Description |
|---|---|
NamingConvention | Method/variable naming violations |
LineLength | Lines exceeding 120 characters |
MagicNumber | Hardcoded numeric literals |
JavadocMethod | Missing Javadoc on public methods |
CyclomaticComplexity | Methods with complexity > 10 |

cd ..
Exercise 5: Run golangci-lint on Go (cq-demo-app-005)
Navigate to the Go demo app and run golangci-lint:
cd cq-demo-app-005
golangci-lint run ./...
Common golangci-lint findings:
| Linter | Description |
|---|---|
errcheck | Unchecked error return values |
ineffassign | Assignments to variables that are never used afterward |
staticcheck | Advanced static analysis |
govet | Reports suspicious constructs |
unused | Unused variables, functions, or types |

Run with SARIF output (golangci-lint does not natively output SARIF, but you can use JSON and convert):
golangci-lint run ./... --out-format json > golangci-lint-results.json
cd ..
Exercise 6: Understand the SARIF Format
Open any of the SARIF files generated in the previous exercises. A SARIF result looks like this:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "ESLint",
"rules": [
{
"id": "prefer-const",
"shortDescription": { "text": "Require const for variables that are never reassigned" },
"helpUri": "https://eslint.org/docs/rules/prefer-const"
}
]
}
},
"results": [
{
"ruleId": "prefer-const",
"level": "warning",
"message": { "text": "'x' is never reassigned. Use 'const' instead." },
"locations": [
{
"physicalLocation": {
"artifactLocation": { "uri": "src/utils/helpers.ts" },
"region": { "startLine": 15, "startColumn": 7 }
}
}
]
}
]
}
]
}
Key SARIF concepts:
| Element | Purpose |
|---|---|
tool.driver.name | Identifies the scanning tool |
tool.driver.rules | Rule definitions with descriptions |
results[].ruleId | Links finding to its rule definition |
results[].level | Severity: error, warning, or note |
results[].locations | File path, line number, and column |
results[].message | Human-readable description of the finding |

Verification Checkpoint
Verify your work before continuing:
- ESLint produced warnings/errors for
cq-demo-app-001 - Ruff found violations in
cq-demo-app-002 - .NET Analyzers reported findings for
cq-demo-app-003 - Checkstyle identified violations in
cq-demo-app-004 - golangci-lint found issues in
cq-demo-app-005 - You generated at least one SARIF output file
- You can explain the key fields in a SARIF result
Summary
Per-language linters are the first line of defense in code quality scanning. Each tool specializes in its language ecosystem — ESLint for TypeScript/JavaScript, Ruff for Python, .NET Analyzers for C#, Checkstyle for Java, and golangci-lint for Go. By outputting results in SARIF format, all findings can be aggregated into a unified view in GitHub Security tab or ADO Advanced Security.
Next Steps
Proceed to Lab 03: Complexity Analysis.