Tamp.Eslint.V9
0.1.1
Prefix Reserved
dotnet add package Tamp.Eslint.V9 --version 0.1.1
NuGet\Install-Package Tamp.Eslint.V9 -Version 0.1.1
<PackageReference Include="Tamp.Eslint.V9" Version="0.1.1" />
<PackageVersion Include="Tamp.Eslint.V9" Version="0.1.1" />
<PackageReference Include="Tamp.Eslint.V9" />
paket add Tamp.Eslint.V9 --version 0.1.1
#r "nuget: Tamp.Eslint.V9, 0.1.1"
#:package Tamp.Eslint.V9@0.1.1
#addin nuget:?package=Tamp.Eslint.V9&version=0.1.1
#tool nuget:?package=Tamp.Eslint.V9&version=0.1.1
Tamp.Eslint.V9
Tamp wrapper for the ESLint v9 CLI. Pattern + style + best-practice linter for TypeScript and JavaScript. Emits SARIF via
@microsoft/eslint-formatter-sariffor the Tamp.Sarif ingest chain. Smart binary resolution: project-local → pnpm exec → npm exec → global PATH → pre-flight skip.
| Package | Status |
|---|---|
Tamp.Eslint.V9 |
0.1.0 (initial) |
Install
dotnet add package Tamp.Eslint.V9
Multi-targets net8 / net9 / net10. The wrapper itself is .NET; it shells out to the ESLint CLI which lives in your project (or globally). See Binary resolution for how the wrapper finds it.
Why this exists (and what the existing scanners miss)
The Tamp security pipeline already covers C# via Roslyn analyzers (semantic) and ReSharper InspectCode (style + best-practice). For pattern-based security scanning of TS/JS, Tamp.OpenGrep covers the security-rule families (p/typescript, p/security-audit, p/owasp-top-ten, etc.).
What's still missing for TypeScript / JavaScript codebases: the equivalent of "ReSharper for C#" — a linter that flags style + best-practice issues (unused vars, missing useEffect deps, prefer-const, no-explicit-any, unstable hook ordering, etc.). That's ESLint's domain, and that's what this wrapper enables.
Quick start
Skippable pre-flight (recommended for security pipelines)
using Tamp;
using Tamp.Eslint.V9;
[Solution] readonly Solution Solution = null!;
AbsolutePath WebRoot => RootDirectory / "web";
Target SecurityScanEslint => _ => _
.Description("TS/JS style + best-practice via ESLint v9.")
.Executes(() =>
{
if (!EslintBinaryResolver.IsAvailable(WebRoot))
{
Log.Info("[security] ESLint skipped — no install found at {Dir}", WebRoot);
return;
}
var plan = Eslint.Scan(s => s
.SetWorkingDirectory(WebRoot)
.AddTarget("src")
.AddTarget("tests")
.SetSarif()
.SetOutputFile(RootDirectory / "artifacts" / "security" / "eslint.sarif")
.SetMaxWarnings(0)
.SetQuiet());
var exit = ProcessRunner.Execute(plan);
// ESLint: 0 = clean, 1 = findings, 2+ = tool/config error.
if (exit > 1) throw new InvalidOperationException($"ESLint failed with exit {exit}.");
});
Direct invocation (when you know ESLint is installed)
var plan = Eslint.Scan(s => s
.AddTarget("web/src")
.SetSarif()
.SetOutputFile("artifacts/security/eslint.sarif")
.SetWorkingDirectory("web")
.SetQuiet());
ProcessRunner.Execute(plan);
Verb surface (v1)
| Verb | Wraps | Required |
|---|---|---|
Scan |
eslint <flags> <targets> |
At least one AddTarget(...); OutputFile required when Sarif is true |
Out of scope for v1 (file as follow-up tickets if adopters ask):
--fix/ autofix — mutating source from a security-scan wrapper is a foot-gun; explicit opt-in only.- Caching flags (
--cache,--cache-location) — adopter-side concern. --rulesdir— non-flat-config legacy mechanism.- Other formatters beyond the SARIF + arbitrary string override.
Binary resolution
ESLint isn't shipped through dotnet tool or any Tamp-native install attribute. The wrapper resolves the binary at Scan(...) time in priority order:
- Project-local —
{WorkingDirectory}/node_modules/.bin/eslint(oreslint.cmdon Windows). Preferred because the project's exact pinned ESLint + plugin versions run. - pnpm exec —
pnpm exec eslint -- .... Used whenpnpm-lock.yamlorpnpm-workspace.yamlis present atWorkingDirectoryANDpnpmis onPATH. - npm exec —
npm exec eslint -- .... Used whenpackage-lock.jsonis present atWorkingDirectoryANDnpmis onPATH. - Global —
eslintonPATH. - Not found —
ToCommandPlan()throwsInvalidOperationExceptionwith an actionable message.
Pre-flighting (skip when not installed)
if (!EslintBinaryResolver.IsAvailable(workingDirectory))
{
// Log and skip the target — same posture as the rest of the security pipeline.
return;
}
Overriding resolution explicitly
var binary = new EslintBinaryResolution
{
Executable = "/opt/ci/eslint/9.30.0/eslint",
Source = EslintResolutionSource.Explicit,
};
var plan = Eslint.Scan(s => s
.SetBinary(binary)
.AddTarget("src"));
SARIF integration
Set SetSarif(true) to emit SARIF 2.1.0 via @microsoft/eslint-formatter-sarif. The formatter is a separate npm package that must be present in the project's devDependencies:
# In your web project root:
pnpm add -D @microsoft/eslint-formatter-sarif
# or
npm install -D @microsoft/eslint-formatter-sarif
The wrapper just emits --format @microsoft/eslint-formatter-sarif --output-file <path>. The Tamp.Sarif reader ingests the produced SARIF unchanged.
Exit-code semantics
ESLint follows the standard linter convention:
| Exit | Meaning | Treat as |
|---|---|---|
0 |
Clean run, no findings | success |
1 |
Findings reported | success (findings are still a successful scan) |
2+ |
Tool or config error | failure |
A security-pipeline target should only throw on exit > 1. This mirrors the Tamp.OpenGrep exit-code convention.
Settings authoring — fluent or object-init
Both produce identical CommandPlans; fluent is canonical in docs.
// Fluent
Eslint.Scan(s => s
.AddTarget("src")
.SetSarif()
.SetOutputFile("out.sarif"));
// Object-init
var settings = new EslintScanSettings
{
Sarif = true,
OutputFile = "out.sarif",
};
settings.Targets.Add("src");
Eslint.Scan(settings);
Minimal ESLint v9 flat-config example for TypeScript + React
For reference — adopters typically already have an eslint.config.js. If you don't:
// eslint.config.js — at the root of your web/ project
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
plugins: {
react,
'react-hooks': reactHooks,
},
rules: {
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
},
settings: { react: { version: 'detect' } },
},
];
devDependencies:
pnpm add -D eslint @eslint/js typescript-eslint \
eslint-plugin-react eslint-plugin-react-hooks \
@microsoft/eslint-formatter-sarif
Why not Biome / OXLint instead
Both are faster and ergonomically nicer. Open to either as additional satellites later (Tamp.Biome, Tamp.OxLint). ESLint is the incumbent with the largest rule ecosystem (@typescript-eslint, eslint-plugin-react, thousands of community plugins) — best baseline for first coverage.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.