@@ -3,33 +3,100 @@ const {visitIf} = require('enhance-visitors');
3
3
const util = require ( '../util' ) ;
4
4
const createAvaRule = require ( '../create-ava-rule' ) ;
5
5
6
- const isMethod = name => util . executionMethods . has ( name ) ;
6
+ class MicroCorrecter {
7
+ constructor ( words ) {
8
+ this . words = new Set ( words ) ;
9
+
10
+ const letters = new Set ( ) ;
11
+ words . forEach ( word => word . split ( '' ) . forEach ( letter => letters . add ( letter ) ) ) ;
12
+ this . letters = [ ...letters ] ;
13
+ }
14
+
15
+ edits ( word ) {
16
+ const edits = [ ] ;
17
+ const { length} = word ;
18
+ const { letters} = this ;
19
+
20
+ for ( let i = 0 ; i < length ; i ++ ) {
21
+ edits . push ( word . slice ( 0 , i ) + word . slice ( i + 1 ) ) ; // Skip
22
+ for ( const letter of letters ) {
23
+ edits . push ( word . slice ( 0 , i ) + letter + word . slice ( i + 1 ) ) ; // Replace
24
+ }
25
+ }
26
+
27
+ for ( let i = 1 ; i < length ; i ++ ) {
28
+ edits . push ( word . slice ( 0 , i - 1 ) + word [ i ] + word [ i - 1 ] + word . slice ( i + 1 ) ) ; // Transposition
29
+ }
30
+
31
+ for ( let i = 0 ; i <= length ; i ++ ) {
32
+ for ( const letter of letters ) {
33
+ edits . push ( word . slice ( 0 , i ) + letter + word . slice ( i ) ) ; // Addition
34
+ }
35
+ }
36
+
37
+ return edits ;
38
+ }
39
+
40
+ correct ( word , distance ) {
41
+ const { words} = this ;
42
+
43
+ if ( words . has ( word ) ) {
44
+ return word ;
45
+ }
46
+
47
+ if ( distance > 0 ) {
48
+ const edits = this . edits ( word ) ;
49
+
50
+ for ( const edit of edits ) {
51
+ if ( words . has ( edit ) ) {
52
+ return edit ;
53
+ }
54
+ }
55
+
56
+ if ( distance > 1 ) {
57
+ for ( const edit of edits ) {
58
+ const correction = this . correct ( edit , distance - 1 ) ;
59
+ if ( correction !== undefined ) {
60
+ return correction ;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ const nonMethods = new Set ( [
69
+ 'context' ,
70
+ 'title'
71
+ ] ) ;
72
+
73
+ const properties = new Set ( [
74
+ ...nonMethods ,
75
+ ...util . executionMethods ,
76
+ 'skip'
77
+ ] ) ;
78
+
79
+ const correcter = new MicroCorrecter ( [ ...properties ] ) ;
7
80
8
81
const isCallExpression = node =>
9
82
node . parent . type === 'CallExpression' &&
10
83
node . parent . callee === node ;
11
84
12
- const getMemberStats = members => {
13
- const initial = {
14
- skip : [ ] ,
15
- falsey : [ ] ,
16
- method : [ ] ,
17
- other : [ ]
18
- } ;
19
-
20
- return members . reduce ( ( res , member ) => {
21
- if ( member === 'skip' ) {
22
- res . skip . push ( member ) ;
23
- } else if ( member === 'falsey' ) {
24
- res . falsey . push ( member ) ;
25
- } else if ( isMethod ( member ) ) {
26
- res . method . push ( member ) ;
27
- } else {
28
- res . other . push ( member ) ;
29
- }
85
+ const correctIfNeeded = ( name , context , node ) => {
86
+ const correction = correcter . correct ( name , Math . max ( 0 , Math . min ( name . length - 2 , 2 ) ) ) ;
87
+ if ( correction === undefined ) {
88
+ return undefined ;
89
+ }
90
+
91
+ if ( correction !== name ) {
92
+ context . report ( {
93
+ node,
94
+ message : `Misspelled \`.${ correction } \` as \`.${ name } \`.` ,
95
+ fix : fixer => fixer . replaceText ( node . property , correction )
96
+ } ) ;
97
+ }
30
98
31
- return res ;
32
- } , initial ) ;
99
+ return correction ;
33
100
} ;
34
101
35
102
const create = context => {
@@ -58,66 +125,62 @@ const create = context => {
58
125
}
59
126
60
127
const members = util . getMembers ( node ) ;
61
- const stats = getMemberStats ( members ) ;
62
-
63
- if ( members [ 0 ] === 'context' ) {
64
- // Anything is fine when of the form `t.context...`
65
- if ( members . length === 1 && isCallExpression ( node ) ) {
66
- // Except `t.context()`
67
- context . report ( {
68
- node,
69
- message : 'Unknown assertion method `.context`.'
70
- } ) ;
71
- }
72
128
73
- return ;
74
- }
129
+ let hadSkip = false ;
130
+ let hadCall = false ;
131
+ let needCall = true ;
132
+ for ( const [ i , member ] of members . entries ( ) ) {
133
+ const corrected = correctIfNeeded ( member , context , node ) ;
134
+ if ( corrected === undefined ) {
135
+ needCall = false ;
136
+ if ( isCallExpression ( node ) ) {
137
+ context . report ( {
138
+ node,
139
+ message : `Unknown assertion method \`.${ member } \`.`
140
+ } ) ;
141
+ } else {
142
+ context . report ( {
143
+ node,
144
+ message : `Unknown member \`.${ member } \`. Use \`.context.${ member } \` instead.`
145
+ } ) ;
146
+ }
75
147
76
- if ( members [ 0 ] === 'title' ) {
77
- // Anything is fine when of the form `t.title...`
78
- if ( members . length === 1 && isCallExpression ( node ) ) {
79
- // Except `t.title()`
80
- context . report ( {
81
- node,
82
- message : ' Unknown assertion method `.title`.'
83
- } ) ;
84
- }
148
+ break ;
149
+ } else if ( i === 0 && nonMethods . has ( corrected ) ) {
150
+ needCall = false ;
151
+ if ( members . length === 1 && isCallExpression ( node ) ) {
152
+ context . report ( {
153
+ node,
154
+ message : ` Unknown assertion method \`. ${ member } \`.`
155
+ } ) ;
156
+ }
85
157
86
- return ;
87
- }
158
+ break ;
159
+ } else if ( corrected === 'skip' ) {
160
+ if ( hadSkip ) {
161
+ context . report ( {
162
+ node,
163
+ message : 'Too many chained uses of `.skip`.'
164
+ } ) ;
165
+ }
166
+
167
+ hadSkip = true ;
168
+ } else {
169
+ if ( hadCall ) {
170
+ context . report ( {
171
+ node,
172
+ message : 'Can\'t chain assertion methods.'
173
+ } ) ;
174
+ }
88
175
89
- if ( isCallExpression ( node ) ) {
90
- if ( stats . other . length > 0 ) {
91
- context . report ( {
92
- node,
93
- message : `Unknown assertion method \`.${ stats . other [ 0 ] } \`.`
94
- } ) ;
95
- } else if ( stats . skip . length > 1 ) {
96
- context . report ( {
97
- node,
98
- message : 'Too many chained uses of `.skip`.'
99
- } ) ;
100
- } else if ( stats . falsey . length > 0 ) {
101
- context . report ( {
102
- node,
103
- message : 'Misspelled `.falsy` as `.falsey`.' ,
104
- fix : fixer => fixer . replaceText ( node . property , 'falsy' )
105
- } ) ;
106
- } else if ( stats . method . length > 1 ) {
107
- context . report ( {
108
- node,
109
- message : 'Can\'t chain assertion methods.'
110
- } ) ;
111
- } else if ( stats . method . length === 0 ) {
112
- context . report ( {
113
- node,
114
- message : 'Missing assertion method.'
115
- } ) ;
176
+ hadCall = true ;
116
177
}
117
- } else if ( stats . other . length > 0 ) {
178
+ }
179
+
180
+ if ( needCall && ! hadCall ) {
118
181
context . report ( {
119
182
node,
120
- message : `Unknown member \`. ${ stats . other [ 0 ] } \`. Use \`.context. ${ stats . other [ 0 ] } \` instead.`
183
+ message : 'Missing assertion method.'
121
184
} ) ;
122
185
}
123
186
} )
0 commit comments