feat(linter/typescript): implement explicit-member-accessibility#21447
Merged
camc314 merged 7 commits intooxc-project:mainfrom Apr 15, 2026
Merged
Conversation
7519972 to
21d2136
Compare
a0859c7 to
b5aab3c
Compare
Adds the `explicit-member-accessibility` rule from `@typescript-eslint`, which requires or disallows explicit accessibility modifiers on class properties, methods, accessors, and parameter properties. Made-with: Cursor
b5aab3c to
d255319
Compare
typescript/explicit-member-accessibility
Merging this PR will not alter performance
Comparing Footnotes
|
This was referenced Apr 20, 2026
camc314
added a commit
that referenced
this pull request
Apr 20, 2026
# Oxlint ### 💥 BREAKING CHANGES - 24fb7eb allocator: [**BREAKING**] Rename `Box` and `Vec` methods (#21395) (overlookmotel) ### 🚀 Features - 38d8090 linter/jest: Implemented jest `version` settings in config file. (#21522) (Said Atrahouch) - 7dbbb99 linter/eslint: Implement suggestion for `no-case-declarations` rule (#21508) (Mikhail Baev) - 9b4d9f6 linter/prefer-template: Implement autofix (#21502) (François) - daa64ed linter/no-empty-pattern: Add `allowObjectPatternsAsParameters` option (#21474) (camc314) - cf2d281 linter/typescript: Implement explicit-member-accessibility (#21447) (Hunter Tunnicliff) - d48de6f linter/unicorn: Add help messages to 3 rule diagnostics (#21459) (Mukunda Rao Katta) - cffdc2e linter: Backfill rule version metadata (#21391) (Old Autumn) ### 🐛 Bug Fixes - 1e69b91 linter/no-useless-assignment: Improve diagnostic spans (#21581) (camc314) - f272594 linter/plugins: Align `RuleMeta.replacedBy` type with ESLint (#21544) (bab) - 4d57851 linter/eslint: Enhance `no-empty-function` rule to support async and generator functions in VariableDeclarator (#21542) (Mikhail Baev) - 00fc136 codegen: Preserve coverage comments before object properties (#21312) (bab) - a56b7b9 oxlint: Dont enable gitlab formatter by default (#21501) (camc314) - 9c9b6a2 linter/array-callback-return: Ignore non-exit CFG dead ends (#21497) (camc314) - 61088e0 linter/unicorn: Handle computed property access in `prefer-dom-node-remove` rule (#21470) (Mikhail Baev) - eab5934 linter: Report an error on unsupported `extends` values (#21406) (John Costa) - 3289ba0 linter/valid-expect-in-promise: Check a jest fn to be `test` instead of `describe` (#21422) (Said Atrahouch) - 4417fe3 linter/prefer-ending-with-an-expect: Ignore vi.mock factory callbacks (#21414) (Cédric Exbrayat) - a904883 linter/consistent-type-imports: Ignore vue/svelte/astro files (#21415) (bab) - 2498fe6 linter/no-unused-vars: Allow segments of dotted namespace declarations (#21416) (bab) - 44b5b35 linter: Preserve vitest-compatible jest rules when applying overrides (#21389) (Cameron) - 7bd8331 linter/prefer-ending-with-an-expect: Add missing `version` docs (#21390) (Said Atrahouch) - 43d8f0d linter/no-useless-assignment: Ignore writes read by closures (#21380) (camc314) ### 📚 Documentation - c1eeae3 linter: Add version to `rule.json` (#21547) (camchenry) - 0ec6ab2 linter: Improve the `vitest/no-importing-vitest-globals` rule documentation. (#21557) (connorshea) # Oxfmt ### 💥 BREAKING CHANGES - 24fb7eb allocator: [**BREAKING**] Rename `Box` and `Vec` methods (#21395) (overlookmotel) ### 🚀 Features - 5aa7fe1 oxfmt: Add `--disable-nested-config` CLI flag (#21514) (leaysgur) - b5cb8d1 oxfmt: Update prettier to 3.8.3 (#21451) (leaysgur) - 16713d5 oxfmt/cli: Support per-directory config (#21103) (leaysgur) - 952de06 oxfmt/lsp: Support per-directory config (#21081) (leaysgur) ### 🐛 Bug Fixes - a501a53 formatter: Handle comments after pipe in single-member union types (#21487) (John Costa) - 6f49fad oxfmt: Respect nested config.`ignorePatterns` (#21489) (leaysgur) - 7c98d52 oxfmt: Do not panic on finding invalid nested config (#21461) (leaysgur) - 41bb2d5 formatter: Preserve more `intrinsic` parens (#21449) (leaysgur) - f894750 formatter: Preserve parens around `intrinsic` in type alias annotation (#21410) (Dunqing) ### ⚡ Performance - df27b48 oxfmt: Skip ancestors check when no nested config found (#21517) (leaysgur) - 5e1522a oxfmt: Do not occupy the rayon thread solely for handover (#21408) (leaysgur) Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds the
typescript/explicit-member-accessibilityrule for #2180 and was generated using Cursor and Opus 4.6 Extra High.I don't have much Rust experience, but I have worked with linters for many years and use oxlint at my company on a large monorepo.
Here's the plan I refined with Cursor to faciliate this PR, if this is of use:
name: explicit-member-accessibility rule
overview: Implement the
typescript/explicit-member-accessibilityrule that requires (or forbids) explicitpublic/private/protectedmodifiers on class members, with per-member-kind overrides and autofix support.todos:
content: Run
just new-typescript-rule explicit-member-accessibilityto generate boilerplate and register the rulestatus: completed
content: Define
AccessibilityLevelenum, config struct withaccessibility,ignoredMethodNames,overrides, and implementfrom_configurationstatus: completed
content: Define
missing_accessibilityandunwanted_public_accessibilitydiagnostic helper functionsstatus: completed
content: Implement
Rule::runforMethodDefinition-- check constructors, methods, getters/setters with correct override resolutionstatus: completed
content: Implement
Rule::runforPropertyDefinitionandAccessorPropertystatus: completed
content: Implement
Rule::runforFormalParameter(parameter properties)status: completed
content: Implement fix (remove
public) and suggestions (addpublic/private/protected)status: completed
content: Add comprehensive test cases covering all config permutations, member types, private identifiers, and fixes
status: completed
content: Run
just fmt,cargo test -p oxc_linter, andjust readystatus: completed
isProject: false
Implement
typescript/explicit-member-accessibilityReference
Rule behavior
The rule has three accessibility-level modes:
"explicit"(default) -- every class member must have an explicitpublic,private, orprotectedmodifier"no-public"-- thepublickeyword is forbidden (the other two are fine)"off"-- no checkingA top-level
accessibilityoption sets the default. Per-member-kindoverridescan specialize behavior for:constructors,methods,accessors,properties,parameterProperties.An
ignoredMethodNamesoption (string array) skips named methods.Fixes:
"no-public"violations get a fix that removes thepublickeyword"explicit"violations get suggestions (not auto-fix) offering to addpublic,private, orprotectedMembers with
#privateidentifier keys are always skipped (JS private fields already have inherent privacy).Key AST types
All live in
crates/oxc_ast/src/ast/js.rsandcrates/oxc_ast/src/ast/ts.rs:MethodDefinition-- hasaccessibility: Option<TSAccessibility>,kind: MethodDefinitionKind,key: PropertyKeyPropertyDefinition-- hasaccessibility: Option<TSAccessibility>,key: PropertyKeyAccessorProperty-- hasaccessibility: Option<TSAccessibility>,key: PropertyKeyFormalParameter-- hasaccessibility: Option<TSAccessibility>,readonly: bool,has_modifier()(parameter properties)TSAccessibility-- enum:Public,Protected,PrivatePropertyKey::is_private_identifier()-- detect#fooprivate fieldsImplementation plan
1. Scaffold with
just new-typescript-ruleThis generates crates/oxc_linter/src/rules/typescript/explicit_member_accessibility.rs, registers the module in crates/oxc_linter/src/rules.rs, and runs codegen.
2. Define config struct
In the new rule file, define the configuration following the pattern in no_extraneous_class.rs:
Implement
Rule::from_configurationto parse the JSON options (array with one object element), resolving each override against the baseaccessibility.3. Implement
Rule::runMatch on three
AstKindvariants:AstKind::MethodDefinition(method): Skip ifkey.is_private_identifier(). Determine the effective check level based onmethod.kind(Constructor ->ctorCheck, Get/Set ->accessorCheck, Method ->methodCheck). Skip if the method name is inignoredMethodNames. Then:"no-public"+accessibility == Some(Public)-> reportunwantedPublicAccessibilitywith a fix removingpublic"explicit"+accessibility.is_none()-> reportmissingAccessibilitywith suggestions to add each modifierAstKind::PropertyDefinition(prop): Skip ifkey.is_private_identifier(). UsepropCheck. Same check/report pattern.AstKind::AccessorProperty(prop): Skip ifkey.is_private_identifier(). UsepropCheck(following the upstream which usespropCheckfor all property-like members). Same check/report pattern.For parameter properties, match on
AstKind::FormalParameter(param)whenparam.has_modifier(). UseparamPropCheck. The"no-public"case only applies whenaccessibility == Some(Public)ANDreadonly == true(matches upstream behavior). The"explicit"case reports whenaccessibility.is_none()(the param hasreadonly/overridebut no explicit visibility keyword).4. Diagnostics
Define helper functions:
5. Fixes and suggestions
public(no-publicviolations): Usefixer.delete_range(...)targeting thepublickeyword span. The span can be computed asSpan::new(node.span.start, <first-non-public-token-start>)-- but we need to be careful about decorators. A simpler approach: search for "public " in the source text within the member's span and remove it.explicitviolations): Usectx.diagnostic_with_suggestion(...)with suggestions forpublic,private, andprotected. Insert text before the member's key (or before the first token after decorators).6. Tests
Add comprehensive test cases following the upstream test file patterns, using
Tester:{ accessibility: "explicit" }-- missing modifiers on methods, properties, accessors, constructors, parameter properties{ accessibility: "no-public" }-- unwantedpublicon each member type{ accessibility: "off" }-- no reports{ accessibility: "no-public", overrides: { properties: "explicit" } })ignoredMethodNamesoption#foo) members are always skippedpublicremoval, suggestion tests for adding modifiers7. Finalize
just fmtcargo test -p oxc_linterto verify tests passcargo insta reviewif needed to accept snapshotsjust readyfor final checks