
ESLint 規則深度解析no-unused-private-class-members 檢測未使用的私有類成員【免費下載鏈接】eslintFind and fix problems in your JavaScript code.項目地址: https://gitcode.com/GitHub_Trending/es/eslint本文基于 ESLint 官方文檔 docs/src/rules/no-unused-private-class-members.md 編寫并參考了 lib/rules/no-unused-private-class-members.js 源碼及其測試用例 tests/lib/rules/no-unused-private-class-members.js從使用層面和實現層面完整剖析該規則。規則概覽no-unused-private-class-members是 ESLint 內置的一條problem 型發現問題規則其作用是報告聲明了但從未被使用的私有類成員。這里的私有類成員指 JavaScript 中以#開頭的私有字段private field、私有方法private method與私有訪問器private accessor它們是 ES2022 正式納入規范的類私有特性。這類成員一旦聲明卻從未被讀取或訪問通常是不徹底的重構留下的殘留成員既占用了代碼空間又容易讓讀者產生困惑——它是否仍被外部依賴是否還有隱藏用途該規則通過靜態分析給出明確答案幫助開發者清理這些死代碼。規則的核心判定邏輯出自官方文檔 Rule Details非常簡單且嚴格私有字段或私有方法其值從未被讀取即視為未使用私有訪問器getter/setter從未被訪問無論是讀取還是寫入即視為未使用。需要特別強調的是對于普通字段只寫不讀例如只在方法里被賦值、卻從未被讀取同樣會被判定為未使用——這是該規則區別于no-unused-vars的關鍵設計詳見下文只寫不讀也算未使用一節。規則配置與啟用方式配置項該規則沒有任何可配置選項schema: []見 lib/rules/no-unused-private-class-members.js在配置文件中只能選擇開啟或關閉無法通過參數調整其行為邊界// eslint.config.jsflat config 寫法 export default [ { rules: { no-unused-private-class-members: error, }, }, ];若使用舊式.eslintrc寫法則為{ rules: { no-unused-private-class-members: error } }推薦配置中的位置從源碼元數據看lib/rules/no-unused-private-class-members.js該規則標注為recommended: true即它已被納入 ESLint 的推薦規則集。在 packages/js/src/configs/eslint-recommended.js 中可以看到它被配置為error級別。也就是說只要你的項目啟用了eslint:recommendedflat config 中為eslint/js導出的recommended配置該規則就會默認以 error 級別生效無需手動開啟。引入版本與類型聲明根據 docs/src/_data/rule_versions.json 的記錄該規則于ESLint 8.1.0版本引入。在 lib/types/rules.d.ts 中其 TypeScript 類型聲明為Linter.RuleEntry[]同樣印證了它不接受任何選項空元組類型。報告消息當規則觸發時默認報告消息為{{classMemberName}} is defined but never used.例如實際輸出形如#unusedMember is defined but never used.消息模板定義見 lib/rules/no-unused-private-class-members.js。觸發規則的不正確代碼示例以下是官方文檔給出的全部錯誤示例逐一解釋其觸發原因/*eslint no-unused-private-class-members: error*/ class A { #unusedMember 5; }類A私有字段#unusedMember聲明后從未被讀取直接觸發。class B { #usedOnlyInWrite 5; method() { this.#usedOnlyInWrite 42; } }類B私有字段#usedOnlyInWrite雖然在方法中被寫入 42但從未被讀取依然觸發——這是只寫不讀場景。class C { #usedOnlyToUpdateItself 5; method() { this.#usedOnlyToUpdateItself; } }類C私有字段#usedOnlyToUpdateItself只被自增自增的結果從未被進一步使用同樣觸發。class D { #unusedMethod() {} }類D私有方法#unusedMethod從未被調用觸發。class E { get #unusedAccessor() {} set #unusedAccessor(value) {} }類E私有訪問器#unusedAccessorgetter 與 setter 成對聲明從未被訪問——既沒有讀取也沒有寫入觸發。不觸發規則的正確代碼示例以下示例展示了被使用的判定邊界/*eslint no-unused-private-class-members: error*/ class A { #usedMember 42; method() { return this.#usedMember; } }類A#usedMember在method()中被return讀取視為已使用。class B { #usedMethod() { return 42; } anotherMethod() { return this.#usedMethod(); } }類B私有方法#usedMethod被anotherMethod()調用視為已使用。class C { get #usedAccessor() {} set #usedAccessor(value) {} method() { this.#usedAccessor 42; } }類C私有訪問器#usedAccessor在method()中被寫入 42。根據規則定義訪問器只要被訪問讀或寫即視為已使用因為 getter/setter 的定義體中可能包含副作用side effects不能因為只寫就斷定其無意義。何時不使用此規則官方文檔明確指出如果你不想收到關于未使用私有類成員的提示可以放心地關閉此規則。{ rules: { no-unused-private-class-members: off } }常見的關閉場景包括類成員帶有反射、裝飾器或框架機制運行時可能通過非直接路徑訪問私有成員雖然從語言層面私有成員只能由類內部訪問但某些元編程場景仍可能出現靜態分析判定未使用的誤報項目正處于重構過渡期希望先保留一批待接入的私有方法/字段避免被報錯干擾團隊風格上不介意保留備用成員且已通過其他手段如代碼評審控制質量。深入源碼規則是如何判定未使用的理解該規則的最佳途徑是閱讀其實現 lib/rules/no-unused-private-class-members.js。整個實現圍繞三個 AST 節點訪問器展開形成聲明收集 → 使用標記 → 出口報告的三段式流水線。第一步ClassBody 入口收集全部私有成員當進入一個類的ClassBody節點時lib/rules/no-unused-private-class-members.js規則把該類聲明過的所有私有成員放入一個Map并假定它們默認全部未使用ClassBody(classBodyNode) { const privateMembers new Map(); trackedClasses.unshift(privateMembers); for (const bodyMember of classBodyNode.body) { if ( bodyMember.type PropertyDefinition || bodyMember.type MethodDefinition ) { if (bodyMember.key.type PrivateIdentifier) { privateMembers.set(bodyMember.key.name, { declaredNode: bodyMember, hasReference: false, isAccessor: bodyMember.type MethodDefinition (bodyMember.kind set || bodyMember.kind get), }); } } } }這里有兩個值得注意的設計細節trackedClasses是一個棧數組新類被unshift到棧頂。這是因為類可以嵌套如方法內部return class { ... }棧結構保證內層類先處理、外層類后處理私有成員名可以正確地歸屬到各自所在的類而不會跨類混淆。每個成員記錄三個字段declaredNode聲明節點、hasReference是否出現過任何引用、isAccessor是否為 getter/setter。isAccessor用于區分普通方法與訪問器因為二者的使用判定標準不同。第二步PrivateIdentifier 訪問器逐一標記已使用每當代碼中出現一個#name形式的私有標識符時lib/rules/no-unused-private-class-members.js規則從棧中自上而下查找包含該名稱的類PrivateIdentifier(privateIdentifierNode) { const classBody trackedClasses.find(classProperties classProperties.has(privateIdentifierNode.name), ); // ... if (memberDefinition.isUsed) { return; } // 聲明本身的 #name 不計為使用 if ( privateIdentifierNode.parent.type PropertyDefinition || privateIdentifierNode.parent.type MethodDefinition ) { return; } memberDefinition.hasReference true; // 訪問器任何訪問讀或寫都算使用 if (memberDefinition.isAccessor) { memberDefinition.isUsed true; return; } // 純寫賦值不算使用 if (isWriteOnlyAssignment(privateIdentifierNode)) { return; } // 自增/解構模式等邊界情況…… memberDefinition.isUsed true; }關鍵判斷依次為聲明位置本身的#name即#unusedMember 5中作為PropertyDefinition.key的標識符不計入使用否則規則將永遠無法觸發訪問器getter/setter任何讀寫訪問一律視為已使用源碼注釋明確指出getter/setter 定義中可能帶有副作用普通字段/方法繼續檢查是否為純寫場景。第三步isWriteOnlyAssignment識別只寫不讀isWriteOnlyAssignment函數lib/rules/no-unused-private-class-members.js是區分寫入與讀寫的核心。它的邏輯可以概括為只有當父節點是AssignmentExpression且運算符為純、ForInStatement、ForOfStatement或AssignmentPattern且該成員位于賦值左側parentStatement.left時才可能是純寫復合賦值運算符如、-通常視為讀 寫因為右側隱式讀取了當前值——但有一個例外如果這個復合賦值的結果被丟棄在一條空表達式語句中ExpressionStatement即this.#x 42;單獨成句那么它仍然被當作純寫處理對應官方文檔中類B的示例自增/自減this.#x單獨成句時同樣被判定為純寫對應類C的示例。第四步ClassBody:exit報告剩余未使用成員在類體遍歷結束時lib/rules/no-unused-private-class-members.js從棧頂彈出該類收集的成員表凡isUsed仍為false的成員一律報告ClassBody:exit() { const unusedPrivateMembers trackedClasses.shift(); for (const [classMemberName, { declaredNode, hasReference, isUsed }] of unusedPrivateMembers.entries()) { if (isUsed) { continue; } context.report({ node: declaredNode, loc: declaredNode.key.loc, messageId: unusedPrivateClassMember, data: { classMemberName: #${classMemberName} }, suggest: [ /* 見下文自動修復 */ ], }); } }由于私有成員在語言層面只能被當前類內部訪問這是 JavaScript 私有字段的硬性約束規則可以安全地斷言所有對#name的引用都必然出現在其聲明類的代碼范圍內因此不存在跨類使用的漏判問題。源碼注釋也明確說明了這一安全前提。規則的邊界行為從測試用例看判定細節測試文件共 1400 余行覆蓋了大量邊界場景是理解該規則行為邊界的權威參考。以下是幾類代表性用例。只寫不讀與復合賦值class Foo { #usedOnlyInWrite 5; method() { this.#usedOnlyInWrite 42; // 純賦值無讀取 → 報錯 } }class Foo { #usedOnlyInWriteStatement 5; method() { this.#usedOnlyInWriteStatement 42; // 結果被丟棄 → 報錯 } }class C { #usedOnlyInIncrement; foo() { this.#usedOnlyInIncrement; // 自增結果被丟棄 → 報錯 } }這三例分別對應純賦值、復合賦值單獨成句、自增單獨成句三種寫后即棄場景均被判定為未使用。視為已使用的讀寫場景反向來看只要寫入的結果被消費過就不再觸發規則class C { #usedMember; foo() { bar(this.#usedMember 1); // 的結果被作為參數讀取 → 已使用 } }class Foo { #usedInForOfLoop; method() { for (const bar of this.#usedInForOfLoop) { } // 被迭代 → 已使用 } }class C { #usedInObjectAssignment; method() { ({ [this.#usedInObjectAssignment]: a } foo); // 作為計算屬性鍵 → 已使用 } }相反作為解構賦值的目標位置純寫入模式則不算使用// ({ x: this.#unusedInDestructuring } bar); → 報錯 // [...this.#unusedInRestPattern] bar; → 報錯 // [this.#unusedInAssignmentPattern] bar; → 報錯訪問器的特殊規則只要 getter/setter 被訪問過一次無論讀還是寫整對訪問器都視為已使用class C { set #accessorWithSetterFirst(value) { doSomething(value); } get #accessorWithSetterFirst() { return something(); } method() { this.#accessorWithSetterFirst 1; // 觸發讀寫 → 已使用 } }嵌套類的作用域隔離測試中專門覆蓋了嵌套類場景驗證trackedClasses棧機制的正確性。例如內層類聲明并使用了與外層同名的私有成員時外層同名成員因從未被引用而依然報錯反之只有內層類使用、外層從未引用時外層成員照常報錯。源碼注釋lib/rules/no-unused-private-class-members.js解釋了為何用標記isUsed而非刪除成員的方式處理刪除會導致后續引用錯誤地命中外層類的同名成員從而產生誤判。自動修復一條內置的 Suggestion該規則在meta中聲明了hasSuggestions: truelib/rules/no-unused-private-class-members.js意味著它會為每個問題提供一條建議suggestion級修復——刪除未使用的私有類成員。注意它不是自動修復fix需要編輯器或--fix-type suggestion配合由開發者確認后手動應用。修復的前提沒有外部引用修復器的核心邏輯位于 lib/rules/no-unused-private-class-members.js*fix(fixer) { if (hasReference) { return; // 存在引用哪怕是只寫引用時不提供刪除建議 } const removalRange getMemberRemovalRange(declaredNode); const semicolonInsertionToken getSemicolonInsertionToken(declaredNode); // ...刪除并視情況補充分號 }也就是說只有當該成員從未出現過任何形式的引用hasReference為 false時才提供整體刪除的建議。對于只寫不讀的成員有引用但無讀取規則會報告錯誤但不提供刪除建議——因為刪除一個被賦值的成員會改變程序行為風險過高。注釋的保留策略刪除成員時如何處理其上的注釋是這項建議最精細的部分。源碼中getLeadingCommentslib/rules/no-unused-private-class-members.js、getTrailingCommentslib/rules/no-unused-private-class-members.js與getMemberRemovalRangelib/rules/no-unused-private-class-members.js協同實現了如下策略緊貼成員的前導注釋如/** docs */、// remove me隨成員一起刪除行內注釋如/* remove */ #unusedMember 1;中位于同行的塊注釋一并刪除若注釋行與相鄰的其他代碼共享同一行如/* keep */ #unusedMember 1; foo 1則保留注釋因為該注釋可能描述的是剩余代碼而非被刪成員若塊注釋跨行且與成員分離如foo 1; /*\n */ #unusedMember 1;只刪除成員本身注釋結構盡量保留行尾注釋#unusedMember 1; // trailing隨成員刪除但當行尾還有其他代碼時bar; #unused2; // keep注釋保留因為// keep語義上可能屬于bar;刪除成員后若其后的#name令牌緊跟前一成員且無法安全銜接修復器還會通過getSemicolonInsertionTokenlib/rules/no-unused-private-class-members.js自動補充分號避免破壞類體的語法。以下測試用例直觀展示了修復輸出// 輸入 class Foo { /** docs */ #unusedMember 1; } // 應用建議后 class Foo { }// 輸入共享同一行的注釋被保留 class Foo { /* keep */ #unusedMember 1; foo 1 } // 應用建議后 class Foo { /* keep */ foo 1 }// 輸入行尾注釋指向其他代碼時被保留 class C { bar; #unused2; // keep } // 應用建議后 class C { bar; // keep }與 no-unused-vars 的關系與差異熟悉 ESLint 的開發者可能會聯想到no-unused-vars未使用變量檢測。二者的分工不同no-unused-vars也包含對類成員的檢查但它對**私有成員只做是否存在任何引用**層面的判定即只要#member被賦值過就不會報告no-unused-private-class-members則更進一步要求值必須被讀取。因此一個只在方法里被賦值、從不被讀取的私有字段no-unused-vars會放過而no-unused-private-class-members會報錯。換言之這條規則把不可達的死代碼檢測延伸到了可寫但不可讀的成員對重構殘留的識別更嚴格尤其適合捕捉那些賦值了卻沒人消費的狀態字段。實踐建議默認開啟由于該規則已進入eslint:recommended多數項目無需額外配置即可受益。若使用 flat config確認已引入eslint/js的recommended配置即可。配合編輯器建議啟用編輯器的quick fix提示對無引用的未使用私有成員可一鍵刪除對只寫不讀的成員規則只報錯不自動刪除需要人工判斷是補上讀取邏輯還是刪除賦值。重構收尾利器在大型類中提取方法或移動邏輯后極易殘留失去引用的私有字段運行npx eslint --rule no-unused-private-class-members: error src/即可快速掃描定位。必要的關閉場景當私有成員會被運行時反射、代碼生成或框架內部機制間接觸碰靜態分析無法看到時可在文件級或規則級關閉該規則避免誤報。延伸閱讀規則實現源碼lib/rules/no-unused-private-class-members.js規則測試用例邊界行為全集tests/lib/rules/no-unused-private-class-members.jsESLint 推薦規則集配置packages/js/src/configs/eslint-recommended.js規則版本記錄docs/src/_data/rule_versions.json類型聲明lib/types/rules.d.ts規則索引lib/rules/index.js【免費下載鏈接】eslintFind and fix problems in your JavaScript code.項目地址: https://gitcode.com/GitHub_Trending/es/eslint創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考