@@ -15,6 +15,82 @@ function isNullOrUndefined(node: TSESTree.Expression): boolean {
1515 return node . type === 'Identifier' && node . name === 'undefined' ;
1616}
1717
18+ const nonArrayReturningMethods = new Set ( [
19+ 'charAt' ,
20+ 'charCodeAt' ,
21+ 'codePointAt' ,
22+ 'endsWith' ,
23+ 'includes' ,
24+ 'indexOf' ,
25+ 'join' ,
26+ 'lastIndexOf' ,
27+ 'localeCompare' ,
28+ 'normalize' ,
29+ 'padEnd' ,
30+ 'padStart' ,
31+ 'repeat' ,
32+ 'replace' ,
33+ 'replaceAll' ,
34+ 'startsWith' ,
35+ 'substring' ,
36+ 'toExponential' ,
37+ 'toFixed' ,
38+ 'toLocaleLowerCase' ,
39+ 'toLocaleString' ,
40+ 'toLocaleUpperCase' ,
41+ 'toLowerCase' ,
42+ 'toPrecision' ,
43+ 'toString' ,
44+ 'toUpperCase' ,
45+ 'trim' ,
46+ 'trimEnd' ,
47+ 'trimStart'
48+ ] ) ;
49+ const primitiveCoercionFunctions = new Set ( [
50+ 'BigInt' ,
51+ 'Boolean' ,
52+ 'Number' ,
53+ 'String'
54+ ] ) ;
55+
56+ function isKnownNonArrayConcatReceiver ( node : TSESTree . Node ) : boolean {
57+ switch ( node . type ) {
58+ case 'Literal' :
59+ case 'ObjectExpression' :
60+ case 'TemplateLiteral' :
61+ return true ;
62+ }
63+
64+ if ( node . type !== 'CallExpression' ) {
65+ return false ;
66+ }
67+
68+ const { callee} = node ;
69+ if (
70+ callee . type === 'Identifier' &&
71+ primitiveCoercionFunctions . has ( callee . name )
72+ ) {
73+ return true ;
74+ }
75+
76+ if (
77+ callee . type !== 'MemberExpression' ||
78+ callee . property . type !== 'Identifier'
79+ ) {
80+ return false ;
81+ }
82+
83+ if (
84+ callee . object . type === 'Identifier' &&
85+ callee . object . name === 'JSON' &&
86+ callee . property . name === 'stringify'
87+ ) {
88+ return true ;
89+ }
90+
91+ return nonArrayReturningMethods . has ( callee . property . name ) ;
92+ }
93+
1894export const preferSpreadSyntax : TSESLint . RuleModule < MessageIds , [ ] > = {
1995 meta : {
2096 type : 'suggestion' ,
@@ -59,15 +135,19 @@ export const preferSpreadSyntax: TSESLint.RuleModule<MessageIds, []> = {
59135 node . callee . object . name === 'Buffer'
60136 )
61137 ) {
62- // If type info is available, only flag when the receiver is an array
63- if ( isArrayType ( node . callee . object , context ) === false ) {
138+ const receiver = node . callee . object ;
139+ const receiverArrayType = isArrayType ( receiver , context ) ;
140+ if (
141+ receiverArrayType === false ||
142+ ( receiverArrayType !== true &&
143+ isKnownNonArrayConcatReceiver ( receiver ) )
144+ ) {
64145 return ;
65146 }
66147
67148 const parts : string [ ] = [ ] ;
68149
69150 // For array literals, inline elements; otherwise spread
70- const receiver = node . callee . object ;
71151 if ( receiver . type === 'ArrayExpression' ) {
72152 for ( const el of receiver . elements ) {
73153 if ( el ) {
0 commit comments