@timmo001/oxlint-rules 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,12 +5,21 @@ Shared Oxlint plugins and configs built around the unchanged
5
5
  locally owned generic rules under `timmo` and Effect rules under
6
6
  `timmo-effect`.
7
7
 
8
- Oxlint's JavaScript plugin API is alpha. Consumers must keep `oxlint` and
9
- `@oxlint/plugins` on the exact peer versions declared by this package.
8
+ This package targets the latest stable Oxlint toolchain. Oxlint's
9
+ [JavaScript plugin API is alpha](https://oxc.rs/docs/guide/usage/linter/config.html)
10
+ and is outside its semver guarantees, so each rules release declares the exact
11
+ `oxlint` and `@oxlint/plugins` versions it has validated. Older toolchains and
12
+ untested future versions are not part of the support contract.
10
13
 
11
14
  ## Install
12
15
 
13
- Install from npm with the package manager already used by the repository:
16
+ Check the published rules package's `peerDependencies` against the official npm
17
+ `latest` versions of `oxlint` and `@oxlint/plugins`. If they differ, a validated
18
+ rules release is needed before upgrading. Do not bypass peer checks or override
19
+ Vite Plus's bundled dependencies to make an unsupported version fit.
20
+
21
+ Once the versions match, install from npm with the package manager already used
22
+ by the repository:
14
23
 
15
24
  ```sh
16
25
  bun add --dev --exact @timmo001/oxlint-rules oxlint @oxlint/plugins
@@ -42,6 +51,11 @@ Both configs can be installed from JSR with its npm compatibility support.
42
51
  npmjs.org remains the default for package managers that resolve ordinary
43
52
  package names from `node_modules`.
44
53
 
54
+ For type-aware linting, also pin the latest `oxlint-tsgolint` version that meets
55
+ Oxlint's declared peer requirement and has passed the package check. Enable
56
+ `options: { typeAware: true }` in the consumer's config. The shared configs do
57
+ not enable type-aware linting themselves.
58
+
45
59
  ## Copy rules
46
60
 
47
61
  Copy a reviewed snapshot when a repository should own the rule source:
@@ -60,12 +74,20 @@ replace an existing destination unless `--force` is passed.
60
74
  ### `anti-slop` and `anti-slop-effect`
61
75
 
62
76
  See the upstream [rule documentation](https://github.com/dmmulroy/anti-slop#rules).
77
+ The recommended configs disable
78
+ `anti-slop/require-safety-comment-for-type-assertion` in favour of
79
+ `timmo/prefer-types-over-assertions`. The upstream rule remains available for
80
+ explicit use.
63
81
 
64
82
  ### `timmo`
65
83
 
66
84
  - `prefer-event-parameter-type`: reports assertions on a handler parameter's
67
85
  `target` or `currentTarget`. Express the target type in the function signature
68
86
  instead so every use sees the same contract.
87
+ - `prefer-types-over-assertions`: reports non-`const` TypeScript assertions,
88
+ even when preceded by a `SAFETY:` comment. Type values where they enter the
89
+ code, for example in function parameters or a checked DOM lookup helper that
90
+ returns `null` when the element is missing or has the wrong type.
69
91
 
70
92
  ### `timmo-effect`
71
93
 
@@ -83,14 +105,16 @@ Initialise the upstream source and run the package checks:
83
105
  git submodule update --init --recursive
84
106
  mise run check
85
107
  mise run build
108
+ mise run test:package
86
109
  npm pack --dry-run
87
- bunx jsr publish --dry-run
110
+ bunx jsr@0.14.3 publish --dry-run --allow-dirty
88
111
  ```
89
112
 
90
113
  `bun run lint` builds the local plugins before linting all maintained code,
91
114
  including tests, tooling, the root config and packaged skill scripts. The root
92
- config extends the local `recommended-effect` source config, enabling every
93
- registered rule in `anti-slop`, `timmo`, `anti-slop-effect` and `timmo-effect`.
115
+ config extends the local `recommended-effect` source config, enabling registered
116
+ rules in `anti-slop`, `timmo`, `anti-slop-effect` and `timmo-effect` except for
117
+ the upstream safety-comment requirement described above.
94
118
  Plugin imports resolve through this package's exports to the fresh `dist` build.
95
119
 
96
120
  Lint disables nested configs so the vendored submodule's config cannot override
@@ -98,6 +122,29 @@ the root exclusions. Generated output, vendored source and agent directories
98
122
  remain excluded. RuleTester fixture strings retain their intentional violations;
99
123
  the surrounding test code is linted with the full rule set.
100
124
 
125
+ `mise run test:package` builds and packs once, then installs the tarball in an
126
+ isolated consumer using the exact toolchain pins from `package.json` and strict
127
+ peer checks. It verifies both recommended configs, diagnostics from all four
128
+ shared namespaces, and valid and invalid type-aware examples. CI runs this
129
+ through the existing package workflow's build step. It also requires matching,
130
+ exact Oxlint development and peer versions.
131
+
132
+ ### Toolchain updates and releases
133
+
134
+ Renovate's shared preset groups Oxlint, `@oxlint/plugins` and `oxlint-tsgolint`.
135
+ The local rule keeps both development and peer dependencies pinned instead of
136
+ widening peer ranges. For each update:
137
+
138
+ 1. Verify the official npm `latest` versions and Oxlint's tsgolint peer
139
+ requirement. Keep Oxlint and plugins on the same latest version and regenerate
140
+ the lockfile with the selected tsgolint version.
141
+ 2. Run the checks and package dry-runs above. Fix any API incompatibility before
142
+ accepting the new pins; do not disable rules or bypass peer failures.
143
+ 3. Review and merge the validated update. Consumers need a separately approved
144
+ version bump and release to receive the new peer contract. Publishing a
145
+ GitHub release triggers the existing npm and JSR workflows; both must succeed
146
+ before the update is available to consumers.
147
+
101
148
  Advance `vendor/anti-slop` only by updating its Git submodule commit. Keep its
102
149
  source and MIT licence unchanged, then run the full package checks so both
103
150
  upstream exports and composed configs are verified.
package/dist/cli.js CHANGED
@@ -2604,11 +2604,38 @@ var preferEventParameterTypeRule = defineRule18({
2604
2604
  }
2605
2605
  });
2606
2606
 
2607
+ // src/generic/rules/prefer-types-over-assertions.ts
2608
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2609
+ var preferTypesOverAssertionsRule = defineRule19({
2610
+ meta: {
2611
+ type: "problem",
2612
+ docs: {
2613
+ description: "Prefer typing a value at its source over a TypeScript type assertion."
2614
+ },
2615
+ messages: {
2616
+ avoidAssertion: "Type this value at its source, such as a function parameter or DOM lookup, instead of asserting it here. A SAFETY comment is not a fix."
2617
+ }
2618
+ },
2619
+ create(context) {
2620
+ const checkAssertion = (node) => {
2621
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
2622
+ return;
2623
+ }
2624
+ context.report({ node, messageId: "avoidAssertion" });
2625
+ };
2626
+ return {
2627
+ TSAsExpression: checkAssertion,
2628
+ TSTypeAssertion: checkAssertion
2629
+ };
2630
+ }
2631
+ });
2632
+
2607
2633
  // src/generic/index.ts
2608
2634
  var timmoPlugin = eslintCompatPlugin2({
2609
2635
  meta: { name: "timmo" },
2610
2636
  rules: {
2611
- "prefer-event-parameter-type": preferEventParameterTypeRule
2637
+ "prefer-event-parameter-type": preferEventParameterTypeRule,
2638
+ "prefer-types-over-assertions": preferTypesOverAssertionsRule
2612
2639
  }
2613
2640
  });
2614
2641
  var generic_default = timmoPlugin;
@@ -2629,7 +2656,8 @@ var recommended = defineConfig({
2629
2656
  ],
2630
2657
  rules: {
2631
2658
  ...enablePluginRules("anti-slop", src_default),
2632
- ...enablePluginRules("timmo", generic_default)
2659
+ ...enablePluginRules("timmo", generic_default),
2660
+ "anti-slop/require-safety-comment-for-type-assertion": "off"
2633
2661
  }
2634
2662
  });
2635
2663
  var recommended_default = recommended;
@@ -2638,7 +2666,7 @@ var recommended_default = recommended;
2638
2666
  import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
2639
2667
 
2640
2668
  // src/effect/rules/no-try-catch-in-effect-generators.ts
2641
- import { defineRule as defineRule19 } from "@oxlint/plugins";
2669
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
2642
2670
  function resolveVariable2(sourceCode, identifier) {
2643
2671
  let scope = sourceCode.getScope(identifier);
2644
2672
  while (scope) {
@@ -2704,7 +2732,7 @@ function isRecognisedEffectGenerator(sourceCode, owner) {
2704
2732
  const factoryCall = parent.callee;
2705
2733
  return factoryCall.type === "CallExpression" && isEffectMethod(sourceCode, factoryCall.callee, "fn");
2706
2734
  }
2707
- var noTryCatchInEffectGeneratorsRule = defineRule19({
2735
+ var noTryCatchInEffectGeneratorsRule = defineRule20({
2708
2736
  meta: {
2709
2737
  type: "problem",
2710
2738
  docs: {
@@ -2742,7 +2770,7 @@ var effect_default = timmoEffectPlugin;
2742
2770
  import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
2743
2771
 
2744
2772
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
2745
- import { defineRule as defineRule20 } from "@oxlint/plugins";
2773
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
2746
2774
 
2747
2775
  // vendor/anti-slop/src/effect/shared/tagged-values.ts
2748
2776
  var equalityOperators = new Set(["==", "===", "!=", "!=="]);
@@ -2789,7 +2817,7 @@ var isMatchPatternObject = (node) => {
2789
2817
  };
2790
2818
 
2791
2819
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
2792
- var noManualEffectErrorTagRule = defineRule20({
2820
+ var noManualEffectErrorTagRule = defineRule21({
2793
2821
  meta: {
2794
2822
  type: "problem",
2795
2823
  docs: {
@@ -2826,8 +2854,8 @@ var noManualEffectErrorTagRule = defineRule20({
2826
2854
  });
2827
2855
 
2828
2856
  // vendor/anti-slop/src/effect/rules/no-manual-tag-comparison.ts
2829
- import { defineRule as defineRule21 } from "@oxlint/plugins";
2830
- var noManualTagComparisonRule = defineRule21({
2857
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
2858
+ var noManualTagComparisonRule = defineRule22({
2831
2859
  meta: {
2832
2860
  type: "problem",
2833
2861
  docs: {
@@ -2857,8 +2885,8 @@ var noManualTagComparisonRule = defineRule21({
2857
2885
  });
2858
2886
 
2859
2887
  // vendor/anti-slop/src/effect/rules/no-manual-tagged-construction.ts
2860
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2861
- var noManualTaggedConstructionRule = defineRule22({
2888
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2889
+ var noManualTaggedConstructionRule = defineRule23({
2862
2890
  meta: {
2863
2891
  type: "problem",
2864
2892
  docs: {
@@ -2883,7 +2911,7 @@ var noManualTaggedConstructionRule = defineRule22({
2883
2911
  });
2884
2912
 
2885
2913
  // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
2886
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2914
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
2887
2915
  var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
2888
2916
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
2889
2917
  function isProjectLocalImport(source) {
@@ -2894,7 +2922,7 @@ function getImportedName(specifier) {
2894
2922
  return specifier.imported.name;
2895
2923
  return specifier.imported.value;
2896
2924
  }
2897
- var noServiceConstructorImportsRule = defineRule23({
2925
+ var noServiceConstructorImportsRule = defineRule24({
2898
2926
  meta: {
2899
2927
  type: "problem",
2900
2928
  docs: {
@@ -2928,9 +2956,9 @@ var noServiceConstructorImportsRule = defineRule23({
2928
2956
  });
2929
2957
 
2930
2958
  // vendor/anti-slop/src/effect/rules/prefer-effect-match.ts
2931
- import { defineRule as defineRule24 } from "@oxlint/plugins";
2959
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
2932
2960
  var equalityOperators2 = new Set(["==", "===", "!=", "!=="]);
2933
- var preferEffectMatchRule = defineRule24({
2961
+ var preferEffectMatchRule = defineRule25({
2934
2962
  meta: {
2935
2963
  type: "problem",
2936
2964
  docs: {
@@ -2957,11 +2957,38 @@ var preferEventParameterTypeRule = defineRule24({
2957
2957
  }
2958
2958
  });
2959
2959
 
2960
+ // src/generic/rules/prefer-types-over-assertions.ts
2961
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
2962
+ var preferTypesOverAssertionsRule = defineRule25({
2963
+ meta: {
2964
+ type: "problem",
2965
+ docs: {
2966
+ description: "Prefer typing a value at its source over a TypeScript type assertion."
2967
+ },
2968
+ messages: {
2969
+ avoidAssertion: "Type this value at its source, such as a function parameter or DOM lookup, instead of asserting it here. A SAFETY comment is not a fix."
2970
+ }
2971
+ },
2972
+ create(context) {
2973
+ const checkAssertion = (node) => {
2974
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
2975
+ return;
2976
+ }
2977
+ context.report({ node, messageId: "avoidAssertion" });
2978
+ };
2979
+ return {
2980
+ TSAsExpression: checkAssertion,
2981
+ TSTypeAssertion: checkAssertion
2982
+ };
2983
+ }
2984
+ });
2985
+
2960
2986
  // src/generic/index.ts
2961
2987
  var timmoPlugin = eslintCompatPlugin4({
2962
2988
  meta: { name: "timmo" },
2963
2989
  rules: {
2964
- "prefer-event-parameter-type": preferEventParameterTypeRule
2990
+ "prefer-event-parameter-type": preferEventParameterTypeRule,
2991
+ "prefer-types-over-assertions": preferTypesOverAssertionsRule
2965
2992
  }
2966
2993
  });
2967
2994
  var generic_default = timmoPlugin;
@@ -2977,7 +3004,8 @@ var recommended = defineConfig({
2977
3004
  ],
2978
3005
  rules: {
2979
3006
  ...enablePluginRules("anti-slop", src_default),
2980
- ...enablePluginRules("timmo", generic_default)
3007
+ ...enablePluginRules("timmo", generic_default),
3008
+ "anti-slop/require-safety-comment-for-type-assertion": "off"
2981
3009
  }
2982
3010
  });
2983
3011
  var recommended_default = recommended;
@@ -2599,11 +2599,38 @@ var preferEventParameterTypeRule = defineRule18({
2599
2599
  }
2600
2600
  });
2601
2601
 
2602
+ // src/generic/rules/prefer-types-over-assertions.ts
2603
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2604
+ var preferTypesOverAssertionsRule = defineRule19({
2605
+ meta: {
2606
+ type: "problem",
2607
+ docs: {
2608
+ description: "Prefer typing a value at its source over a TypeScript type assertion."
2609
+ },
2610
+ messages: {
2611
+ avoidAssertion: "Type this value at its source, such as a function parameter or DOM lookup, instead of asserting it here. A SAFETY comment is not a fix."
2612
+ }
2613
+ },
2614
+ create(context) {
2615
+ const checkAssertion = (node) => {
2616
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
2617
+ return;
2618
+ }
2619
+ context.report({ node, messageId: "avoidAssertion" });
2620
+ };
2621
+ return {
2622
+ TSAsExpression: checkAssertion,
2623
+ TSTypeAssertion: checkAssertion
2624
+ };
2625
+ }
2626
+ });
2627
+
2602
2628
  // src/generic/index.ts
2603
2629
  var timmoPlugin = eslintCompatPlugin2({
2604
2630
  meta: { name: "timmo" },
2605
2631
  rules: {
2606
- "prefer-event-parameter-type": preferEventParameterTypeRule
2632
+ "prefer-event-parameter-type": preferEventParameterTypeRule,
2633
+ "prefer-types-over-assertions": preferTypesOverAssertionsRule
2607
2634
  }
2608
2635
  });
2609
2636
  var generic_default = timmoPlugin;
@@ -2624,7 +2651,8 @@ var recommended = defineConfig({
2624
2651
  ],
2625
2652
  rules: {
2626
2653
  ...enablePluginRules("anti-slop", src_default),
2627
- ...enablePluginRules("timmo", generic_default)
2654
+ ...enablePluginRules("timmo", generic_default),
2655
+ "anti-slop/require-safety-comment-for-type-assertion": "off"
2628
2656
  }
2629
2657
  });
2630
2658
  var recommended_default = recommended;
@@ -57,11 +57,38 @@ var preferEventParameterTypeRule = defineRule({
57
57
  }
58
58
  });
59
59
 
60
+ // src/generic/rules/prefer-types-over-assertions.ts
61
+ import { defineRule as defineRule2 } from "@oxlint/plugins";
62
+ var preferTypesOverAssertionsRule = defineRule2({
63
+ meta: {
64
+ type: "problem",
65
+ docs: {
66
+ description: "Prefer typing a value at its source over a TypeScript type assertion."
67
+ },
68
+ messages: {
69
+ avoidAssertion: "Type this value at its source, such as a function parameter or DOM lookup, instead of asserting it here. A SAFETY comment is not a fix."
70
+ }
71
+ },
72
+ create(context) {
73
+ const checkAssertion = (node) => {
74
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
75
+ return;
76
+ }
77
+ context.report({ node, messageId: "avoidAssertion" });
78
+ };
79
+ return {
80
+ TSAsExpression: checkAssertion,
81
+ TSTypeAssertion: checkAssertion
82
+ };
83
+ }
84
+ });
85
+
60
86
  // src/generic/index.ts
61
87
  var timmoPlugin = eslintCompatPlugin({
62
88
  meta: { name: "timmo" },
63
89
  rules: {
64
- "prefer-event-parameter-type": preferEventParameterTypeRule
90
+ "prefer-event-parameter-type": preferEventParameterTypeRule,
91
+ "prefer-types-over-assertions": preferTypesOverAssertionsRule
65
92
  }
66
93
  });
67
94
  var generic_default = timmoPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/oxlint-rules",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Shared Oxlint plugins and configs with optional Effect rules.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -42,16 +42,18 @@
42
42
  "lint": "bun run build && oxlint --disable-nested-config .",
43
43
  "skills:validate": "bunx skills-ref validate skills/install-timmo-oxlint-rules && bunx skills-ref validate skills/add-oxlint-rule && bunx skills-ref validate skills/release-oxlint-rules",
44
44
  "test": "node --experimental-strip-types scripts/test.ts",
45
+ "test:package": "node --experimental-strip-types scripts/test-package.ts",
45
46
  "typecheck": "tsc --noEmit"
46
47
  },
47
48
  "peerDependencies": {
48
- "@oxlint/plugins": "1.83.0",
49
- "oxlint": "1.83.0"
49
+ "@oxlint/plugins": "1.86.0",
50
+ "oxlint": "1.86.0"
50
51
  },
51
52
  "devDependencies": {
52
- "@oxlint/plugins": "1.83.0",
53
+ "@oxlint/plugins": "1.86.0",
53
54
  "@types/bun": "^1.3.14",
54
- "oxlint": "1.83.0",
55
+ "oxlint": "1.86.0",
56
+ "oxlint-tsgolint": "7.0.2003",
55
57
  "prettier": "^3.9.5",
56
58
  "skills-ref": "0.1.5",
57
59
  "typescript": "^7.0.0"
@@ -14,8 +14,12 @@ description: >-
14
14
  instructions, and normal checks. Use the current working directory unless
15
15
  the user names another target.
16
16
  3. Preserve existing ignores, overrides, plugins, and repository-owned rules.
17
- Confirm `oxlint` and `@oxlint/plugins` use the same exact version supported
18
- by the selected package version.
17
+ Verify the official npm `latest` versions of `oxlint` and `@oxlint/plugins`.
18
+ They must match each other and the exact peers of the selected rules release.
19
+ Support is latest-only: if the published peer contract has not caught up,
20
+ report the pending rules release instead of widening peers or bypassing
21
+ package-manager failures. Do not force upgrades of Vite Plus's transitive
22
+ dependencies or assume its bundled plugins satisfy this contract.
19
23
 
20
24
  ## Package
21
25
 
@@ -28,6 +32,10 @@ description: >-
28
32
  instead only when `effect` is a direct dependency or the user explicitly
29
33
  requests it.
30
34
  4. Keep dependency and config edits visible. Do not delegate them to a script.
35
+ 5. For type-aware linting, explicitly pin the latest `oxlint-tsgolint` that
36
+ satisfies Oxlint's peer requirement and was validated with the rules release.
37
+ Enable `options.typeAware` in the target config and verify type-aware linting
38
+ alongside the shared rules.
31
39
 
32
40
  ## Copy rules
33
41
 
@@ -17,22 +17,34 @@ description: >-
17
17
  its release exists. Resolve the next version from the requested major,
18
18
  minor, patch, or exact version. Ask only when the release level is missing.
19
19
  Do not add a `v` prefix when the repository's existing tags omit it.
20
- 3. Update the version in `package.json` and `jsr.json`. Before committing, run
21
- `mise run check`, `mise run build`, `npm pack --dry-run`, and
20
+ 3. Verify official npm `latest` metadata for `oxlint`, `@oxlint/plugins` and
21
+ `oxlint-tsgolint`. Support is latest-only: keep Oxlint and plugins on the same
22
+ latest version, pin their development and peer dependencies exactly, and
23
+ select the latest tsgolint satisfying Oxlint's peer requirement. Regenerate
24
+ `bun.lock`. The shared Renovate preset groups this toolchain; the local rule
25
+ keeps peers pinned. Resolve compatibility failures before releasing, without
26
+ widening peers, disabling rules or forcing transitive dependency upgrades.
27
+ 4. Update the version in `package.json` and `jsr.json` only when a version bump
28
+ is requested. Before committing, run `mise run check`, `mise run build`,
29
+ `mise run test:package`, `npm pack --dry-run`, and
22
30
  `bunx jsr@0.14.3 publish --dry-run --allow-dirty`. The dirty-tree flag is
23
- required because the intended version bump is not committed yet.
24
- 4. Treat a direct request to create or publish the release as authorisation for
31
+ required because the intended changes are not committed yet. The package
32
+ check installs packed output with strict peers and verifies both recommended
33
+ configs, actual shared-rule diagnostics and type-aware linting.
34
+ 5. Treat a direct request to create or publish the release as authorisation for
25
35
  its version commit, push, and GitHub release. Follow the active environment's
26
36
  guarded commit and push workflow, committing only the release metadata with
27
37
  `Release Oxlint rules <version>`. A request to prepare or plan a release does
28
38
  not authorise publication.
29
- 5. Resolve the target only after the push with `git rev-parse HEAD`. Pass the
39
+ 6. Resolve the target only after the push with `git rev-parse HEAD`. Pass the
30
40
  resulting full 40-character SHA as the `--target` value when running
31
41
  `gh release create`. Never pass an abbreviated SHA as `target_commitish`.
32
- 6. Create the release before resolving workflow targets because publication is
42
+ 7. Create the release before resolving workflow targets because publication is
33
43
  triggered by the `release.published` event. Watch only the release commit's
34
44
  exact workflow runs and require both `Publish to npm` and `Publish to JSR` to
35
45
  succeed.
36
- 7. Report the release URL, version commit, validation results, and npm and JSR
46
+ 8. Report the release URL, version commit, validation results, and npm and JSR
37
47
  publication conclusions. Do not report the release as complete while either
38
- publication job is pending or failed.
48
+ publication job is pending or failed. A merged toolchain update alone does
49
+ not deliver its peer contract to consumers; preparation ends with publication
50
+ pending until a version bump and release are approved.
@@ -1,11 +1,13 @@
1
1
  import { eslintCompatPlugin } from "@oxlint/plugins";
2
2
 
3
3
  import { preferEventParameterTypeRule } from "./rules/prefer-event-parameter-type.ts";
4
+ import { preferTypesOverAssertionsRule } from "./rules/prefer-types-over-assertions.ts";
4
5
 
5
6
  const timmoPlugin = eslintCompatPlugin({
6
7
  meta: { name: "timmo" },
7
8
  rules: {
8
9
  "prefer-event-parameter-type": preferEventParameterTypeRule,
10
+ "prefer-types-over-assertions": preferTypesOverAssertionsRule,
9
11
  },
10
12
  });
11
13
 
@@ -0,0 +1,38 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+
3
+ import type { ESTree } from "@oxlint/plugins";
4
+
5
+ type TypeAssertion = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
6
+
7
+ /** Prefer typing values at their source over asserting them at use sites. */
8
+ export const preferTypesOverAssertionsRule = defineRule({
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description:
13
+ "Prefer typing a value at its source over a TypeScript type assertion.",
14
+ },
15
+ messages: {
16
+ avoidAssertion:
17
+ "Type this value at its source, such as a function parameter or DOM lookup, instead of asserting it here. A SAFETY comment is not a fix.",
18
+ },
19
+ },
20
+ create(context) {
21
+ const checkAssertion = (node: TypeAssertion) => {
22
+ if (
23
+ node.typeAnnotation.type === "TSTypeReference" &&
24
+ node.typeAnnotation.typeName.type === "Identifier" &&
25
+ node.typeAnnotation.typeName.name === "const"
26
+ ) {
27
+ return;
28
+ }
29
+
30
+ context.report({ node, messageId: "avoidAssertion" });
31
+ };
32
+
33
+ return {
34
+ TSAsExpression: checkAssertion,
35
+ TSTypeAssertion: checkAssertion,
36
+ };
37
+ },
38
+ });