@@ -10,11 +10,15 @@ import {
1010 type IgnoreAccessorPatternOption ,
1111 type IgnoreClassesOption ,
1212 type IgnoreIdentifierPatternOption ,
13+ type OverridableOptions ,
14+ type RawOverridableOptions ,
15+ getCoreOptions ,
1316 ignoreAccessorPatternOptionSchema ,
1417 ignoreClassesOptionSchema ,
1518 ignoreIdentifierPatternOptionSchema ,
1619 shouldIgnoreClasses ,
1720 shouldIgnorePattern ,
21+ upgradeRawOverridableOptions ,
1822} from "#eslint-plugin-functional/options" ;
1923import {
2024 isExpected ,
@@ -26,6 +30,7 @@ import {
2630 createRule ,
2731 getTypeOfNode ,
2832} from "#eslint-plugin-functional/utils/rule" ;
33+ import { overridableOptionsSchema } from "#eslint-plugin-functional/utils/schemas" ;
2934import {
3035 findRootIdentifier ,
3136 isDefinedByMutableVariable ,
@@ -53,62 +58,61 @@ export const name = "immutable-data";
5358 */
5459export const fullName = `${ ruleNameScope } /${ name } ` ;
5560
61+ type CoreOptions = IgnoreAccessorPatternOption &
62+ IgnoreClassesOption &
63+ IgnoreIdentifierPatternOption & {
64+ ignoreImmediateMutation : boolean ;
65+ ignoreNonConstDeclarations :
66+ | boolean
67+ | {
68+ treatParametersAsConst : boolean ;
69+ } ;
70+ } ;
71+
5672/**
5773 * The options this rule can take.
5874 */
59- type Options = [
60- IgnoreAccessorPatternOption &
61- IgnoreClassesOption &
62- IgnoreIdentifierPatternOption & {
63- ignoreImmediateMutation : boolean ;
64- ignoreNonConstDeclarations :
65- | boolean
66- | {
67- treatParametersAsConst : boolean ;
68- } ;
69- } ,
70- ] ;
75+ type RawOptions = [ RawOverridableOptions < CoreOptions > ] ;
76+ type Options = OverridableOptions < CoreOptions > ;
7177
72- /**
73- * The schema for the rule options.
74- */
75- const schema : JSONSchema4 [ ] = [
78+ const coreOptionsPropertiesSchema = deepmerge (
79+ ignoreIdentifierPatternOptionSchema ,
80+ ignoreAccessorPatternOptionSchema ,
81+ ignoreClassesOptionSchema ,
7682 {
77- type : "object" ,
78- properties : deepmerge (
79- ignoreIdentifierPatternOptionSchema ,
80- ignoreAccessorPatternOptionSchema ,
81- ignoreClassesOptionSchema ,
82- {
83- ignoreImmediateMutation : {
83+ ignoreImmediateMutation : {
84+ type : "boolean" ,
85+ } ,
86+ ignoreNonConstDeclarations : {
87+ oneOf : [
88+ {
8489 type : "boolean" ,
8590 } ,
86- ignoreNonConstDeclarations : {
87- oneOf : [
88- {
91+ {
92+ type : "object" ,
93+ properties : {
94+ treatParametersAsConst : {
8995 type : "boolean" ,
9096 } ,
91- {
92- type : "object" ,
93- properties : {
94- treatParametersAsConst : {
95- type : "boolean" ,
96- } ,
97- } ,
98- additionalProperties : false ,
99- } ,
100- ] ,
97+ } ,
98+ additionalProperties : false ,
10199 } ,
102- } satisfies JSONSchema4ObjectSchema [ "properties" ] ,
103- ) ,
104- additionalProperties : false ,
100+ ] ,
101+ } ,
105102 } ,
103+ ) as NonNullable < JSONSchema4ObjectSchema [ "properties" ] > ;
104+
105+ /**
106+ * The schema for the rule options.
107+ */
108+ const schema : JSONSchema4 [ ] = [
109+ overridableOptionsSchema ( coreOptionsPropertiesSchema ) ,
106110] ;
107111
108112/**
109113 * The default options for the rule.
110114 */
111- const defaultOptions : Options = [
115+ const defaultOptions : RawOptions = [
112116 {
113117 ignoreClasses : false ,
114118 ignoreImmediateMutation : true ,
@@ -128,18 +132,19 @@ const errorMessages = {
128132/**
129133 * The meta data for this rule.
130134 */
131- const meta : NamedCreateRuleCustomMeta < keyof typeof errorMessages , Options > = {
132- type : "suggestion" ,
133- docs : {
134- category : "No Mutations" ,
135- description : "Enforce treating data as immutable." ,
136- recommended : "recommended" ,
137- recommendedSeverity : "error" ,
138- requiresTypeChecking : true ,
139- } ,
140- messages : errorMessages ,
141- schema,
142- } ;
135+ const meta : NamedCreateRuleCustomMeta < keyof typeof errorMessages , RawOptions > =
136+ {
137+ type : "suggestion" ,
138+ docs : {
139+ category : "No Mutations" ,
140+ description : "Enforce treating data as immutable." ,
141+ recommended : "recommended" ,
142+ recommendedSeverity : "error" ,
143+ requiresTypeChecking : true ,
144+ } ,
145+ messages : errorMessages ,
146+ schema,
147+ } ;
143148
144149/**
145150 * Array methods that mutate an array.
@@ -220,16 +225,30 @@ const stringConstructorNewObjectReturningMethods = ["split"];
220225 */
221226function checkAssignmentExpression (
222227 node : TSESTree . AssignmentExpression ,
223- context : Readonly < RuleContext < keyof typeof errorMessages , Options > > ,
224- options : Readonly < Options > ,
225- ) : RuleResult < keyof typeof errorMessages , Options > {
226- const [ optionsObject ] = options ;
228+ context : Readonly < RuleContext < keyof typeof errorMessages , RawOptions > > ,
229+ rawOptions : Readonly < RawOptions > ,
230+ ) : RuleResult < keyof typeof errorMessages , RawOptions > {
231+ const options = upgradeRawOverridableOptions ( rawOptions [ 0 ] ) ;
232+ const rootNode = findRootIdentifier ( node . left ) ?? node . left ;
233+ const optionsToUse = getCoreOptions < CoreOptions , Options > (
234+ rootNode ,
235+ context ,
236+ options ,
237+ ) ;
238+
239+ if ( optionsToUse === null ) {
240+ return {
241+ context,
242+ descriptors : [ ] ,
243+ } ;
244+ }
245+
227246 const {
228247 ignoreIdentifierPattern,
229248 ignoreAccessorPattern,
230249 ignoreNonConstDeclarations,
231250 ignoreClasses,
232- } = optionsObject ;
251+ } = optionsToUse ;
233252
234253 if (
235254 ! isMemberExpression ( node . left ) ||
@@ -285,16 +304,30 @@ function checkAssignmentExpression(
285304 */
286305function checkUnaryExpression (
287306 node : TSESTree . UnaryExpression ,
288- context : Readonly < RuleContext < keyof typeof errorMessages , Options > > ,
289- options : Readonly < Options > ,
290- ) : RuleResult < keyof typeof errorMessages , Options > {
291- const [ optionsObject ] = options ;
307+ context : Readonly < RuleContext < keyof typeof errorMessages , RawOptions > > ,
308+ rawOptions : Readonly < RawOptions > ,
309+ ) : RuleResult < keyof typeof errorMessages , RawOptions > {
310+ const options = upgradeRawOverridableOptions ( rawOptions [ 0 ] ) ;
311+ const rootNode = findRootIdentifier ( node . argument ) ?? node . argument ;
312+ const optionsToUse = getCoreOptions < CoreOptions , Options > (
313+ rootNode ,
314+ context ,
315+ options ,
316+ ) ;
317+
318+ if ( optionsToUse === null ) {
319+ return {
320+ context,
321+ descriptors : [ ] ,
322+ } ;
323+ }
324+
292325 const {
293326 ignoreIdentifierPattern,
294327 ignoreAccessorPattern,
295328 ignoreNonConstDeclarations,
296329 ignoreClasses,
297- } = optionsObject ;
330+ } = optionsToUse ;
298331
299332 if (
300333 ! isMemberExpression ( node . argument ) ||
@@ -349,16 +382,30 @@ function checkUnaryExpression(
349382 */
350383function checkUpdateExpression (
351384 node : TSESTree . UpdateExpression ,
352- context : Readonly < RuleContext < keyof typeof errorMessages , Options > > ,
353- options : Readonly < Options > ,
354- ) : RuleResult < keyof typeof errorMessages , Options > {
355- const [ optionsObject ] = options ;
385+ context : Readonly < RuleContext < keyof typeof errorMessages , RawOptions > > ,
386+ rawOptions : Readonly < RawOptions > ,
387+ ) : RuleResult < keyof typeof errorMessages , RawOptions > {
388+ const options = upgradeRawOverridableOptions ( rawOptions [ 0 ] ) ;
389+ const rootNode = findRootIdentifier ( node . argument ) ?? node . argument ;
390+ const optionsToUse = getCoreOptions < CoreOptions , Options > (
391+ rootNode ,
392+ context ,
393+ options ,
394+ ) ;
395+
396+ if ( optionsToUse === null ) {
397+ return {
398+ context,
399+ descriptors : [ ] ,
400+ } ;
401+ }
402+
356403 const {
357404 ignoreIdentifierPattern,
358405 ignoreAccessorPattern,
359406 ignoreNonConstDeclarations,
360407 ignoreClasses,
361- } = optionsObject ;
408+ } = optionsToUse ;
362409
363410 if (
364411 ! isMemberExpression ( node . argument ) ||
@@ -416,7 +463,7 @@ function checkUpdateExpression(
416463 */
417464function isInChainCallAndFollowsNew (
418465 node : TSESTree . Expression ,
419- context : Readonly < RuleContext < keyof typeof errorMessages , Options > > ,
466+ context : Readonly < RuleContext < keyof typeof errorMessages , RawOptions > > ,
420467) : boolean {
421468 if ( isMemberExpression ( node ) ) {
422469 return isInChainCallAndFollowsNew ( node . object , context ) ;
@@ -488,16 +535,30 @@ function isInChainCallAndFollowsNew(
488535 */
489536function checkCallExpression (
490537 node : TSESTree . CallExpression ,
491- context : Readonly < RuleContext < keyof typeof errorMessages , Options > > ,
492- options : Readonly < Options > ,
493- ) : RuleResult < keyof typeof errorMessages , Options > {
494- const [ optionsObject ] = options ;
538+ context : Readonly < RuleContext < keyof typeof errorMessages , RawOptions > > ,
539+ rawOptions : Readonly < RawOptions > ,
540+ ) : RuleResult < keyof typeof errorMessages , RawOptions > {
541+ const options = upgradeRawOverridableOptions ( rawOptions [ 0 ] ) ;
542+ const rootNode = findRootIdentifier ( node . callee ) ?? node . callee ;
543+ const optionsToUse = getCoreOptions < CoreOptions , Options > (
544+ rootNode ,
545+ context ,
546+ options ,
547+ ) ;
548+
549+ if ( optionsToUse === null ) {
550+ return {
551+ context,
552+ descriptors : [ ] ,
553+ } ;
554+ }
555+
495556 const {
496557 ignoreIdentifierPattern,
497558 ignoreAccessorPattern,
498559 ignoreNonConstDeclarations,
499560 ignoreClasses,
500- } = optionsObject ;
561+ } = optionsToUse ;
501562
502563 // Not potential object mutation?
503564 if (
@@ -517,7 +578,7 @@ function checkCallExpression(
517578 } ;
518579 }
519580
520- const { ignoreImmediateMutation } = optionsObject ;
581+ const { ignoreImmediateMutation } = optionsToUse ;
521582
522583 // Array mutation?
523584 if (
@@ -608,7 +669,7 @@ function checkCallExpression(
608669}
609670
610671// Create the rule.
611- export const rule = createRule < keyof typeof errorMessages , Options > (
672+ export const rule = createRule < keyof typeof errorMessages , RawOptions > (
612673 name ,
613674 meta ,
614675 defaultOptions ,
0 commit comments