Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/portable-oxlint-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stll/oxlint-plugin": minor
---

Publish portable Oxlint rules for unsafe casts, incomplete union-keyed records, and unsanitized DOM HTML sinks.
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@ jobs:

- name: Enforce release intent
# The repository owner publishes this versioned shared contract.
uses: stella/.github/.github/actions/changeset-policy@c47bb87cc82047b5fdc2540983148d4e3efa9a37 # v1.3.0
uses: stella/.github/.github/actions/changeset-policy@5dafee16c28009f17d3bc088f8aecdf9ce47a6d5
with:
release-paths: |
packages/typescript-config/**
packages/oxlint-config/**
packages/oxlint-plugin/**
generated-paths: |
bun.lock
packages/typescript-config/CHANGELOG.md
packages/typescript-config/package.json
packages/oxlint-config/CHANGELOG.md
packages/oxlint-config/package.json
packages/oxlint-plugin/CHANGELOG.md
packages/oxlint-plugin/package.json
package-files: |
packages/typescript-config/package.json
packages/oxlint-config/package.json
packages/oxlint-plugin/package.json
version-file: bun.lock

checks:
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
# accidentally start a release transaction.
- packages/typescript-config/CHANGELOG.md
- packages/oxlint-config/CHANGELOG.md
- packages/oxlint-plugin/CHANGELOG.md
workflow_dispatch:
inputs:
publish_to_npm:
Expand All @@ -33,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
package: [typescript-config, oxlint-config]
package: [typescript-config, oxlint-config, oxlint-plugin]
permissions:
contents: read
steps:
Expand All @@ -60,8 +61,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
if [[ "$PACKAGE" == "oxlint-config" ]]; then
bun --filter @stll/oxlint-config build
if [[ "$PACKAGE" == "oxlint-config" || "$PACKAGE" == "oxlint-plugin" ]]; then
bun --filter "@stll/$PACKAGE" build
fi
mkdir -p release-artifacts
(
Expand All @@ -87,9 +88,10 @@ jobs:
contents: write
id-token: write
# Keep this caller filename stable: npm trusted publishing authorizes it.
uses: stella/.github/.github/workflows/npm-independent-release.yml@c47bb87cc82047b5fdc2540983148d4e3efa9a37 # v1.3.0
uses: stella/.github/.github/workflows/npm-independent-release.yml@b8c0884053fb8d4fb6ba157692189e0f1aae1785 # v1.6.0
with:
artifact-pattern: npm-tarball-*
package-files: |
packages/typescript-config/package.json
packages/oxlint-config/package.json
packages/oxlint-plugin/package.json
6 changes: 4 additions & 2 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ on:
branches: [main]

concurrency:
# Let the active mutation finish; the shared workflow skips obsolete queued
# source revisions before it receives write credentials.
group: release-pr-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: false

permissions: {}

Expand All @@ -16,7 +18,7 @@ jobs:
permissions:
contents: read
# The repository owner publishes this versioned shared contract.
uses: stella/.github/.github/workflows/changeset-release-pr.yml@c47bb87cc82047b5fdc2540983148d4e3efa9a37 # v1.3.0
uses: stella/.github/.github/workflows/changeset-release-pr.yml@5dafee16c28009f17d3bc088f8aecdf9ce47a6d5
with:
bun-version-file: package.json
secrets:
Expand Down
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ This repo intentionally contains only portable tooling policy:
- `@stll/typescript-config`: strict TypeScript config presets.
- `@stll/oxlint-config`: general upstream oxlint rules and the shared
`stella-lowercase` and `no-raw-colors` JS plugins.
- `@stll/oxlint-plugin`: portable static-safety rules for unsafe type
assertions, incomplete union-keyed records, and raw DOM HTML sinks.
- `rust/`: source-of-truth Rust formatting, lint, and Cargo profile templates.
- `rust-lints/`: Dylint libraries for stella-specific Rust rules.

Repo-specific stella rules stay in the consuming repo: custom oxlint plugins,
security rules, i18n rules, generated native artifacts, benchmark exceptions,
and package-specific ignores.
Repo-specific stella rules stay in the consuming repo: domain authorization,
i18n, generated native artifacts, benchmark exceptions, and package-specific
ignores.

## Usage

Install the shared TypeScript and oxlint packages:

```bash
bun add -d @stll/typescript-config @stll/oxlint-config oxlint oxlint-tsgolint typescript
bun add -d @stll/typescript-config @stll/oxlint-config @stll/oxlint-plugin @oxlint/plugins oxlint oxlint-tsgolint typescript
```

The shared defaults require TypeScript 7.0.2 or newer, oxlint 1.75.0 or
Expand Down Expand Up @@ -66,6 +68,25 @@ export default library({
});
```

Add the portable safety rules to an existing Oxlint config:

```ts
import { defineConfig } from "oxlint";
import {
portableSafetyPluginSpecifiers,
portableSafetyRules,
} from "@stll/oxlint-plugin";

export default defineConfig({
jsPlugins: [...portableSafetyPluginSpecifiers],
rules: { ...portableSafetyRules },
});
```

When using `no-unsafe-inner-html`, disable the blanket `react/no-danger` rule;
the portable rule permits static and provably sanitized HTML while rejecting
untrusted values.

`react/react-compiler` is part of the default rule set and requires
**oxlint >= 1.70**. It is a nursery rule upstream, so its diagnostics may
change between oxlint minor versions; re-audit findings after bumping the
Expand Down
30 changes: 24 additions & 6 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
import { library } from "./packages/oxlint-config/src/index";
import {
portableSafetyPluginSpecifiers,
portableSafetyRules,
} from "./packages/oxlint-plugin/src/index";

export default library({
ignorePatterns: ["packages/oxlint-config/dist/"],
jsPlugins: [...portableSafetyPluginSpecifiers],
overrides: [
{
// Oxlint presents plugin AST nodes as intentionally untyped values.
// Type-aware any-flow and condition rules cannot add safety inside the
// visitor implementation; the passive fixtures prove rule behavior.
files: ["packages/oxlint-plugin/src/**/*.ts"],
rules: {
"typescript/no-unsafe-assignment": "off",
"typescript/no-unsafe-member-access": "off",
"typescript/no-unsafe-call": "off",
"typescript/no-unsafe-return": "off",
"typescript/no-unsafe-argument": "off",
"typescript/strict-boolean-expressions": "off",
"typescript/no-unnecessary-condition": "off",
},
},
{
files: ["packages/oxlint-plugin/fixtures/**/*.{ts,tsx}"],
rules: portableSafetyRules,
},
],
});
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
"packages/*"
],
"scripts": {
"build": "bun --filter @stll/oxlint-config build",
"build": "bun --filter @stll/oxlint-config build && bun --filter @stll/oxlint-plugin build",
"changeset": "changeset",
"changeset:version": "changeset version && bun scripts/check-lockfile-workspace-versions.ts --write && bun install --frozen-lockfile",
"check:lockfile-versions": "bun scripts/check-lockfile-workspace-versions.ts",
"typecheck": "bun --filter @stll/oxlint-config typecheck",
"typecheck": "bun --filter @stll/oxlint-config typecheck && bun --filter @stll/oxlint-plugin typecheck",
"test": "bun test",
"test:rust-lints": "cd rust-lints/stella_lints && cargo test",
"lint": "bun run build && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware .",
"lint:fix": "bun --bun oxlint -c oxlint.config.ts --type-aware --fix .",
"format": "oxfmt . \"!.ai/**\" \"!.agents/**\" \"!.claude/**\" \"!AGENTS.md\" \"!CLAUDE.md\" \"!GEMINI.md\"",
"publint": "publint --pack npm packages/typescript-config && publint --pack npm packages/oxlint-config",
"pack:dry-run": "bun run build && (cd packages/typescript-config && npm pack --dry-run) && (cd packages/oxlint-config && npm pack --dry-run)",
"publint": "publint --pack npm packages/typescript-config && publint --pack npm packages/oxlint-config && publint --pack npm packages/oxlint-plugin",
"pack:dry-run": "bun run build && (cd packages/typescript-config && npm pack --dry-run) && (cd packages/oxlint-config && npm pack --dry-run) && (cd packages/oxlint-plugin && npm pack --dry-run)",
"prepack": "bun run build && bun run typecheck",
"sync-ai": "bash scripts/sync-ai-skills.sh",
"sync-ai:check": "bash scripts/sync-ai-skills.sh --check"
Expand All @@ -27,6 +27,7 @@
"@changesets/changelog-github": "0.7.0",
"@changesets/cli": "2.31.0",
"@stll/oxlint-config": "workspace:*",
"@stll/oxlint-plugin": "workspace:*",
"bun-types": "1.3.14",
"oxfmt": "0.48.0",
"oxlint": "1.75.0",
Expand All @@ -35,5 +36,8 @@
"tsdown": "0.22.9",
"typescript": "7.0.2"
},
"overrides": {
"js-yaml": "3.15.1"
},
"packageManager": "bun@1.3.14"
}
17 changes: 17 additions & 0 deletions packages/oxlint-plugin/fixtures/no-any-casts.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Passive regression fixture for `no-any-casts/no-any-casts`.

declare const value: unknown;

// MUST flag: `as any` bypasses every structural guarantee.
// oxlint-disable-next-line no-any-casts/no-any-casts, typescript/no-explicit-any -- fixture: TypeScript as-any casts must be rejected
export const asAny = value as any;

// MUST flag: angle-bracket any assertions are the same escape hatch.
// oxlint-disable-next-line no-any-casts/no-any-casts, typescript/no-explicit-any -- fixture: legacy any assertions must be rejected
export const angleAny = <any>value;

// Allowed: const assertions preserve literal information without using any.
export const literalTuple = ["workspace", "document"] as const;

// Allowed: satisfies checks a value without laundering its inferred type.
export const checkedTuple = ["workspace"] satisfies readonly string[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Passive regression fixture for
// `no-dangerous-type-assertions/no-dangerous-type-assertions`.

type Payload = {
required: string;
};

// MUST flag: an object-literal cast can conceal a missing required field.
// oxlint-disable-next-line no-dangerous-type-assertions/no-dangerous-type-assertions -- fixture: object literals must be checked instead of cast
export const hiddenMissingField = {} as Payload;

// MUST flag: angle-bracket assertions conceal the same invalid state.
// oxlint-disable-next-line no-dangerous-type-assertions/no-dangerous-type-assertions -- fixture: legacy object-literal casts must be rejected
export const legacyHiddenMissingField = <Payload>{};

// Allowed: satisfies checks every required property without widening.
export const checkedPayload = { required: "present" } satisfies Payload;

// Allowed: `as const` controls literal widening and is explicitly exempt.
export const immutablePayload = { required: "present" } as const;
Loading
Loading