22 * Options for rules.
33 */
44
5+ import {
6+ deepFreezeJsonValue as deepFreezeValue ,
7+ deepFreezeJsonArray as deepFreezeArray ,
8+ deepFreezeJsonObject as deepFreezeObject ,
9+ } from "./json.js" ;
10+
511import type { JsonValue } from "./json.ts" ;
612
13+ const { freeze } = Object ,
14+ { isArray } = Array ,
15+ { min } = Math ;
16+
717/**
818 * Options for a rule on a file.
919 */
@@ -17,3 +27,104 @@ export const allOptions: Readonly<Options>[] = [DEFAULT_OPTIONS];
1727
1828// Index into `allOptions` for default options
1929export const DEFAULT_OPTIONS_ID = 0 ;
30+
31+ /**
32+ * Merge user-provided options from config with rule's default options.
33+ *
34+ * Config options take precedence over default options.
35+ *
36+ * Returned options are deep frozen.
37+ * `ruleOptions` may be frozen in place (or partially frozen) too.
38+ * `defaultOptions` must already be deep frozen before calling this function.
39+ *
40+ * Follows the same merging logic as ESLint's `getRuleOptions`.
41+ * https://github.com/eslint/eslint/blob/0f5a94a84beee19f376025c74f703f275d52c94b/lib/linter/linter.js#L443-L454
42+ * https://github.com/eslint/eslint/blob/0f5a94a84beee19f376025c74f703f275d52c94b/lib/shared/deep-merge-arrays.js
43+ *
44+ * Notably, nested arrays are not merged - config options wins. e.g.:
45+ * - Config options: [ [1] ]
46+ * - Default options: [ [2, 3], 4 ]
47+ * - Merged options: [ [1], 4 ]
48+ *
49+ * @param configOptions - Options from config
50+ * @param defaultOptions - Default options from `rule.meta.defaultOptions`
51+ * @returns Merged options
52+ */
53+ export function mergeOptions (
54+ configOptions : Options | null ,
55+ defaultOptions : Readonly < Options > | null ,
56+ ) : Readonly < Options > {
57+ if ( configOptions === null ) {
58+ return defaultOptions === null ? DEFAULT_OPTIONS : defaultOptions ;
59+ }
60+
61+ if ( defaultOptions === null ) {
62+ deepFreezeArray ( configOptions ) ;
63+ return configOptions ;
64+ }
65+
66+ // Both are defined - merge them
67+ const merged = [ ] ;
68+
69+ const defaultOptionsLength = defaultOptions . length ,
70+ ruleOptionsLength = configOptions . length ,
71+ bothLength = min ( defaultOptionsLength , ruleOptionsLength ) ;
72+
73+ let i = 0 ;
74+ for ( ; i < bothLength ; i ++ ) {
75+ merged . push ( mergeValues ( configOptions [ i ] , defaultOptions [ i ] ) ) ;
76+ }
77+
78+ if ( defaultOptionsLength > ruleOptionsLength ) {
79+ for ( ; i < defaultOptionsLength ; i ++ ) {
80+ merged . push ( defaultOptions [ i ] ) ;
81+ }
82+ } else {
83+ for ( ; i < ruleOptionsLength ; i ++ ) {
84+ const prop = configOptions [ i ] ;
85+ deepFreezeValue ( prop ) ;
86+ merged . push ( prop ) ;
87+ }
88+ }
89+
90+ return freeze ( merged ) ;
91+ }
92+
93+ /**
94+ * Merge value from user-provided options with value from default options.
95+ *
96+ * @param configValue - Value from config
97+ * @param defaultValue - Value from default options
98+ * @returns Merged value
99+ */
100+ function mergeValues ( configValue : JsonValue , defaultValue : JsonValue ) : JsonValue {
101+ // If config value is a primitive, it wins
102+ if ( configValue === null || typeof configValue !== "object" ) return configValue ;
103+
104+ // If config value is an array, it wins
105+ if ( isArray ( configValue ) ) {
106+ deepFreezeArray ( configValue ) ;
107+ return configValue ;
108+ }
109+
110+ // If default value is a primitive or an array, config value wins (it's an object)
111+ if ( defaultValue === null || typeof defaultValue !== "object" || isArray ( defaultValue ) ) {
112+ deepFreezeObject ( configValue ) ;
113+ return configValue ;
114+ }
115+
116+ // Both are objects (not arrays)
117+ const merged = { ...defaultValue , ...configValue } ;
118+
119+ // Symbol properties are not possible in JSON, so no need to handle them here.
120+ // All properties are enumerable own properties, so can use simple `for..in` loop.
121+ for ( const key in configValue ) {
122+ if ( key in defaultValue ) {
123+ merged [ key ] = mergeValues ( configValue [ key ] , defaultValue [ key ] ) ;
124+ } else {
125+ deepFreezeValue ( configValue [ key ] ) ;
126+ }
127+ }
128+
129+ return freeze ( merged ) ;
130+ }
0 commit comments