• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

peterjwest / yason / fe350948-1b8a-4ace-9331-a18394309013

01 Aug 2024 11:39AM UTC coverage: 96.748% (+0.5%) from 96.234%
fe350948-1b8a-4ace-9331-a18394309013

push

circleci

peterjwest
Add snippet config

217 of 226 branches covered (96.02%)

Branch coverage included in aggregate %.

735 of 758 relevant lines covered (96.97%)

131.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.08
/src/MapNode.ts
1
import lodash from 'lodash';
1✔
2

3
import {
4
  EndToken, PrimitiveToken, oneOf,
5
  TrueToken, FalseToken, NullToken,
6
  NumberToken, StringToken,
7
  SymbolToken, ColonToken, DashToken,
8
  LineEndToken, PaddingToken, NewlineToken,
9
} from './tokens';
1✔
10
import Node, { PatternAction, WhitespaceAst } from './Node';
1✔
11
import ListNode, { ListAst } from './ListNode';
1✔
12
import ValueNode, { ValueAst } from './ValueNode';
1✔
13
import KeyNode, { KeyAst } from './KeyNode';
1✔
14
import { JsonMap } from './Json';
15

16
/** Possible MapNode states */
17
export type MapNodeState = (
18
  | 'beforeIndent'
19
  | 'beforeKey'
20
  | 'beforeValue'
21
  | 'beforeNestedValue'
22
  | 'afterValue'
23
  | 'afterItem'
24
);
25

26
/** Nested types which can be pushed on the stack */
27
type MapNestedNode = ListNode | MapNode;
28

29
/** Map AST structure */
30
export interface MapAst {
31
  type: 'Map';
32
  whitespace: WhitespaceAst;
33
  value: MapItemAst[];
34
}
35

36
/** Node reprsenting a map (dictionary) */
37
export default class MapNode extends Node<MapNodeState, MapNestedNode> {
1✔
38
  state: MapNodeState = 'beforeIndent';
18✔
39
  nesting: number;
18✔
40
  value: MapItemNode[] = [];
18✔
41

42
  constructor(nesting: number) {
18✔
43
    super();
14✔
44
    this.nesting = nesting;
14✔
45
  }
14✔
46

47
  /** Get possible actions given a state */
48
  getActions(state: MapNodeState) {
18✔
49
    return MapNode.actions[state];
311✔
50
  }
311✔
51

52
  /** Get raw data for the map */
53
  getData(): JsonMap {
18✔
54
    return lodash.fromPairs(this.value.map((item) => [
14✔
55
      item.key.getData(),
30✔
56
      item.getData(),
30✔
57
    ]));
14✔
58
  }
14✔
59

60
  /** Get AST data for the map */
61
  getAst(): MapAst {
18✔
62
    return {
13✔
63
      type: 'Map',
13✔
64
      whitespace: lodash.pickBy(this.whitespace, (value) => value),
13✔
65
      value: this.value.map((item) => item.getAst()),
13✔
66
    };
13✔
67
  }
13✔
68

69
  static actions: { [key: string]: Array<PatternAction<MapNodeState, MapNestedNode>> } = {
18✔
70
    beforeIndent: [
18✔
71
      {
18✔
72
        pattern: [PaddingToken.optional()],
18✔
73
        action: function(this: MapNode, tokens: [] | [PaddingToken], indent?: string) {
18✔
74
          const tokenIndent = (tokens[0] ? tokens[0].value : '');
30✔
75
          if (indent) {
30✔
76
            if (lodash.repeat(indent, this.nesting) !== tokenIndent) {
12!
77
              throw Error('Invalid indent');
×
78
            }
×
79
          }
12✔
80

81
          // TODO: Check indent is only tabs or spaces
82

83
          this.state = 'beforeKey';
30✔
84
          return { consumed: tokens.length, indent: indent && this.nesting > 0 ? undefined : tokenIndent };
30✔
85
        },
30✔
86
      },
18✔
87
    ],
18✔
88

89
    beforeKey: [
18✔
90
      {
18✔
91
        pattern: [
18✔
92
          oneOf([StringToken, SymbolToken]), PaddingToken.optional(), ColonToken,
18✔
93
        ],
18✔
94
        action: function(
18✔
95
          this: MapNode,
96
          tokens: (
30✔
97
            | [StringToken | SymbolToken, ColonToken]
98
            | [StringToken | SymbolToken, PaddingToken, ColonToken]
99
          ),
100
        ) {
30✔
101
          const item = new MapItemNode();
30✔
102
          item.key = new KeyNode(tokens[0]);
30✔
103

104
          // Move whitespace to next item to be more meaningful
105
          const lastItem = this.value[this.value.length - 1];
30✔
106
          if (lastItem && lastItem.whitespace.after) {
30✔
107
            const index = lastItem.whitespace.after.indexOf('\n');
12✔
108
            if (index !== -1) {
12✔
109
              item.whitespace.before += lastItem.whitespace.after.slice(index + 1);
12✔
110
              lastItem.whitespace.after = lastItem.whitespace.after.slice(0, index);
12✔
111
            }
12✔
112
          }
12✔
113

114
          this.value.push(item);
30✔
115

116
          if (tokens[1] instanceof PaddingToken) {
30✔
117
            item.key.whitespace.inner += tokens[1].value;
1✔
118
          }
1✔
119
          this.state = 'beforeValue';
30✔
120
          return { consumed: tokens.length };
30✔
121
        },
30✔
122
      },
18✔
123
    ],
18✔
124

125
    beforeValue: [
18✔
126
      {
18✔
127
        pattern: [PaddingToken.optional(), PrimitiveToken],
18✔
128
        action: function(
18✔
129
          this: MapNode,
130
          tokens: (
23✔
131
            | [TrueToken | FalseToken | NullToken | NumberToken | StringToken]
132
            | [PaddingToken, TrueToken | FalseToken | NullToken | NumberToken | StringToken]
133
          ),
134
        ) {
23✔
135
          const item = this.value[this.value.length - 1];
23✔
136
          item.value = new ValueNode(tokens[tokens.length - 1]);
23✔
137
          item.value.whitespace.before += tokens[0] instanceof PaddingToken ? tokens[0].value : '';
23✔
138
          this.state = 'afterValue';
23✔
139
          return { consumed: tokens.length };
23✔
140
        },
23✔
141
      },
18✔
142

143
      {
18✔
144
        pattern: [LineEndToken.optional(), NewlineToken],
18✔
145
        action: function(
18✔
146
          this: MapNode,
147
          tokens: Array<LineEndToken | NewlineToken>,
7✔
148
        ) {
7✔
149
          const item = this.value[this.value.length - 1];
7✔
150
          item.key.whitespace.after += tokens[0] instanceof LineEndToken ? tokens[0].value : '';
7✔
151

152
          this.state = 'beforeNestedValue';
7✔
153
          return { consumed: tokens.length };
7✔
154
        },
7✔
155
      },
18✔
156
    ],
18✔
157

158
    beforeNestedValue: [
18✔
159
      {
18✔
160
        pattern: [PaddingToken, oneOf([StringToken, SymbolToken])],
18✔
161
        action: function(this: MapNode) {
18✔
162
          const item = this.value[this.value.length - 1];
6✔
163
          item.value = new MapNode(this.nesting + 1);
6✔
164

165
          // Move whitespace additional lines to item value
166
          if (item.whitespace.after) {
6✔
167
            item.value.whitespace.before += item.whitespace.after;
1✔
168
            item.whitespace.after = '';
1✔
169
          }
1✔
170

171
          this.state = 'afterValue';
6✔
172
          return { push: item.value };
6✔
173
        },
6✔
174
      },
18✔
175
      {
18✔
176
        pattern: [PaddingToken, DashToken],
18✔
177
        action: function(this: MapNode) {
18✔
178
          const item = this.value[this.value.length - 1];
1✔
179
          item.value = new ListNode(this.nesting + 1);
1✔
180

181
          // Move whitespace additional lines to item value
182
          if (item.whitespace.after) {
1✔
183
            item.value.whitespace.before += item.whitespace.after;
1✔
184
            item.whitespace.after = '';
1✔
185
          }
1✔
186

187
          this.state = 'afterItem';
1✔
188
          return { push: item.value };
1✔
189
        },
1✔
190
      },
18✔
191
      {
18✔
192
        pattern: [LineEndToken.optional(), NewlineToken],
18✔
193
        action: function(this: MapNode, tokens: Array<LineEndToken | NewlineToken>) {
18✔
194
          const item = this.value[this.value.length - 1];
2✔
195
          item.whitespace.after += tokens.map((token) => token.value).join('');
2✔
196

197
          return { consumed: tokens.length };
2✔
198
        },
2✔
199
      },
18✔
200
    ],
18✔
201

202
    afterValue: [
18✔
203
      {
18✔
204
        pattern: [LineEndToken.optional(), NewlineToken],
18✔
205
        action: function(this: MapNode, tokens: Array<LineEndToken | NewlineToken>) {
18✔
206
          const item = this.value[this.value.length - 1];
21✔
207
          item.whitespace.after += tokens.map((token) => token.value).join('');
21✔
208
          return { consumed: tokens.length };
21✔
209
        },
21✔
210
      },
18✔
211
      {
18✔
212
        pattern: [],
18✔
213
        action: function(this: MapNode) {
18✔
214
          this.state = 'afterItem';
29✔
215
          return {};
29✔
216
        },
29✔
217
      },
18✔
218
    ],
18✔
219

220
    afterItem: [
18✔
221
      {
18✔
222
        pattern: [PaddingToken.optional(), oneOf([StringToken, SymbolToken, DashToken])],
18✔
223
        action: function(
18✔
224
          this: MapNode,
225
          tokens: (
23✔
226
            | [StringToken | SymbolToken | DashToken]
227
            | [PaddingToken, StringToken | SymbolToken | DashToken]
228
          ),
229
          indent: string,
23✔
230
        ) {
23✔
231
          const item = this.value[this.value.length - 1];
23✔
232

233
          // Hoist value whitespace to item
234
          item.whitespace.after += item.value.whitespace.after;
23✔
235
          item.value.whitespace.after = '';
23✔
236

237
          const tokenIndent = tokens[0] instanceof PaddingToken ? tokens[0].value : '';
23✔
238

239
          // TODO: Improve performance of check
240
          if (lodash.repeat(indent || '', this.nesting) === tokenIndent) {
23✔
241
            this.state = 'beforeIndent';
16✔
242
            return {};
16✔
243
          }
16✔
244

245
          // TODO: Improve performance of check
246
          if (lodash.repeat(indent, this.nesting).slice(0, tokenIndent.length) === tokenIndent) {
7✔
247
            // We're leaving this map, so hoist the whitespace from this item to the map
248
            const index = item.whitespace.after.indexOf('\n');
7✔
249
            if (index !== -1) {
7✔
250
              this.whitespace.after += item.whitespace.after.slice(index + 1);
4✔
251
              item.whitespace.after = item.whitespace.after.slice(0, index);
4✔
252
            }
4✔
253

254
            return { pop: true };
7✔
255
          }
7!
256

257
          throw Error('Invalid indent');
×
258
        },
23✔
259
      },
18✔
260
      {
18✔
261
        pattern: [LineEndToken.optional(), EndToken],
18✔
262
        action: function(this: MapNode, tokens: [EndToken] | [LineEndToken, EndToken]) {
18✔
263
          const item = this.value[this.value.length - 1];
7✔
264
          item.whitespace.after += tokens[0] instanceof LineEndToken ? tokens[0].value : '';
7✔
265

266
          // We're ending the document, so hoist the whitespace from this item to the map
267
          const index = item.whitespace.after.indexOf('\n');
7✔
268
          if (index !== -1) {
7✔
269
            this.whitespace.after += item.whitespace.after.slice(index);
1✔
270
            item.whitespace.after = item.whitespace.after.slice(0, index);
1✔
271
          }
1✔
272

273
          return { consumed: tokens[0] instanceof LineEndToken ? 1 : 0, pop: true };
7✔
274
        },
7✔
275
      },
18✔
276
    ],
18✔
277
  };
18✔
278
}
18✔
279

280
/** MapItem AST structure */
281
interface MapItemAst {
282
  type: 'MapItem';
283
  whitespace: WhitespaceAst;
284
  key: KeyAst;
285
  value: ListAst | MapAst | ValueAst;
286
}
287

288
/** Node representing a map key/value pair */
289
export class MapItemNode extends Node<'', MapNestedNode> {
1✔
290
  key: KeyNode;
30✔
291
  value: MapNestedNode | ValueNode;
30✔
292

293
  /** Get raw data for the map value */
294
  getData() {
30✔
295
    return this.value.getData();
30✔
296
  }
30✔
297

298
  /** Get AST data for key/value pair */
299
  getAst(): MapItemAst {
30✔
300
    return {
29✔
301
      type: 'MapItem',
29✔
302
      whitespace: lodash.pickBy(this.whitespace, (value) => value),
29✔
303
      key: this.key.getAst(),
29✔
304
      value: this.value.getAst(),
29✔
305
    };
29✔
306
  }
29✔
307
}
30✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc