@zone-eu/mailsplit 5.4.9 → 5.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ import type { ContentStream, MimeNode as MimeNodeShape, PartNumber, PartNumberItem, SplitterOptions } from './types';
2
+ import type Headers = require('./headers');
3
+
4
+ /** Parsed MIME node with mutable headers and content encoding helpers. */
5
+ declare class MimeNode implements MimeNodeShape {
6
+ /** Discriminator identifying this chunk as a MIME node. */
7
+ type: 'node';
8
+
9
+ /** Whether this node is the root message node. */
10
+ root: boolean;
11
+
12
+ /** Parent MIME node, or `false` for the root node. */
13
+ parentNode: MimeNodeShape | false;
14
+
15
+ /** Boundary used by this multipart node, or `false` when not multipart. */
16
+ _boundary: Buffer | false;
17
+
18
+ /** Boundary inherited from the parent multipart node, or `false` when absent. */
19
+ _parentBoundary: Buffer | false;
20
+
21
+ /** Length, in bytes, of the raw header block collected for this node. */
22
+ _headerlen: number;
23
+
24
+ /** Multipart subtype such as `mixed` or `alternative`, or `false` for leaf nodes. */
25
+ multipart: string | false;
26
+
27
+ /** Content-Transfer-Encoding value, normalized to lower case, or `false` when absent. */
28
+ encoding: string | false;
29
+
30
+ /** Parsed and mutable header collection, available after headers are parsed. */
31
+ headers: Headers | false;
32
+
33
+ /** MIME content type such as `text/plain`, or `false` when unavailable. */
34
+ contentType: string | false;
35
+
36
+ /** Charset parameter from Content-Type, or `false` when absent. */
37
+ charset: string | false;
38
+
39
+ /** Content-Disposition value such as `inline` or `attachment`, or `false` when absent. */
40
+ disposition: string | false;
41
+
42
+ /** Decoded filename from Content-Disposition or Content-Type parameters, or `false` when absent. */
43
+ filename: string | false;
44
+
45
+ /** Whether this node is `text/*` with `format=flowed`. */
46
+ flowed: boolean;
47
+
48
+ /** Whether flowed text uses `delsp=yes`. */
49
+ delSp: boolean;
50
+
51
+ /** Splitter configuration used when parsing this node. */
52
+ config: SplitterOptions;
53
+
54
+ /** Resolved IMAP-style part number for this node, or `false` before resolution. */
55
+ partNr: PartNumber | false;
56
+
57
+ /** Number of child part numbers allocated by this node. */
58
+ childPartNumbers: number;
59
+
60
+ /** Whether this node's content type is `message/rfc822`. */
61
+ rfc822: boolean;
62
+
63
+ /** Whether an embedded `message/rfc822` node was parsed as a nested message. */
64
+ messageNode?: boolean;
65
+
66
+ /**
67
+ * Creates a MIME node with empty header state.
68
+ *
69
+ * @param parentNode Parent node, or `false`/omitted for the root node.
70
+ * @param config Optional splitter and libmime configuration.
71
+ */
72
+ constructor(parentNode?: MimeNodeShape | false, config?: SplitterOptions);
73
+
74
+ /**
75
+ * Builds the next child part number for this node.
76
+ *
77
+ * @param provided Optional explicit part number item to append.
78
+ * @returns Resolved MIME part number.
79
+ */
80
+ getPartNr(provided?: PartNumberItem): PartNumber;
81
+
82
+ /**
83
+ * Appends one raw header line to this node while parsing.
84
+ *
85
+ * @param line Raw header line bytes; falsy values are ignored.
86
+ * @returns Nothing.
87
+ */
88
+ addHeaderChunk(line?: Buffer | false): void;
89
+
90
+ /**
91
+ * Parses collected header bytes and populates MIME metadata fields.
92
+ *
93
+ * @returns Nothing.
94
+ */
95
+ parseHeaders(): void;
96
+
97
+ /**
98
+ * Builds this node's header block.
99
+ *
100
+ * @returns Header bytes ending with an empty header/body separator line.
101
+ */
102
+ getHeaders(): Buffer;
103
+
104
+ /**
105
+ * Sets or updates the Content-Type header value.
106
+ *
107
+ * @param contentType MIME content type to set; falsy keeps the current type.
108
+ * @returns Nothing.
109
+ */
110
+ setContentType(contentType?: string | false): void;
111
+
112
+ /**
113
+ * Sets, updates, or removes the Content-Type charset parameter.
114
+ *
115
+ * @param charset Charset to set; falsy removes it when possible.
116
+ * @returns Nothing.
117
+ */
118
+ setCharset(charset?: string | false): void;
119
+
120
+ /**
121
+ * Sets, updates, or removes the filename parameter.
122
+ *
123
+ * @param filename Filename to set; falsy removes it when possible.
124
+ * @returns Nothing.
125
+ */
126
+ setFilename(filename?: string | false): void;
127
+
128
+ /**
129
+ * Creates a decoder stream for this node's transfer encoding.
130
+ *
131
+ * @returns Transform stream that outputs decoded content bytes.
132
+ */
133
+ getDecoder(): ContentStream;
134
+
135
+ /**
136
+ * Creates an encoder stream and updates the Content-Transfer-Encoding header when needed.
137
+ *
138
+ * @param encoding Target transfer encoding; defaults to the node's current encoding.
139
+ * @returns Transform stream that outputs encoded content bytes.
140
+ */
141
+ getEncoder(encoding?: string | false): ContentStream;
142
+ }
143
+
144
+ export = MimeNode;
package/lib/mime-node.js CHANGED
@@ -3,52 +3,97 @@
3
3
  const Headers = require('./headers');
4
4
  const libmime = require('libmime');
5
5
  const libqp = require('libqp');
6
+ // @ts-ignore
6
7
  const libbase64 = require('libbase64');
7
8
  const PassThrough = require('stream').PassThrough;
8
9
  const pathlib = require('path');
9
10
 
11
+ /** @typedef {import('..').Headers} HeadersType */
12
+ /** @typedef {import('..').PartNumberItem} PartNumberItem */
13
+ /** @typedef {import('..').PartNumber} PartNumber */
14
+ /** @typedef {import('..').SplitterOptions} SplitterOptions */
15
+ /** @typedef {import('libmime').StructuredHeader} StructuredHeader */
16
+ /** @typedef {import('stream').Transform | import('stream').PassThrough} ContentStream */
17
+ /** @typedef {import('..').MimeNode} MimeNodeType */
18
+
19
+ const Libmime = /** @type {any} */ (libmime.Libmime);
20
+
21
+ /**
22
+ * Parsed MIME node with mutable headers and transfer-encoding helpers.
23
+ */
10
24
  class MimeNode {
25
+ /**
26
+ * @param {MimeNodeType | false} parentNode Parent node, or false for the root node.
27
+ * @param {SplitterOptions} [config] Splitter and libmime configuration.
28
+ */
11
29
  constructor(parentNode, config) {
30
+ /** @type {'node'} */
12
31
  this.type = 'node';
13
32
  this.root = !parentNode;
33
+ /** @type {MimeNodeType | false} */
14
34
  this.parentNode = parentNode;
15
35
 
36
+ /** @type {Buffer | false} */
16
37
  this._parentBoundary = this.parentNode && this.parentNode._boundary;
38
+ /** @type {Buffer[]} */
17
39
  this._headersLines = [];
18
40
  this._headerlen = 0;
19
41
 
42
+ /** @type {StructuredHeader | false} */
20
43
  this._parsedContentType = false;
44
+ /** @type {StructuredHeader | false} */
45
+ this._parsedContentDisposition = false;
46
+ /** @type {Buffer | false} */
21
47
  this._boundary = false;
22
48
 
49
+ /** @type {string | false} */
23
50
  this.multipart = false;
51
+ /** @type {string | false} */
24
52
  this.encoding = false;
53
+ /** @type {HeadersType | false} */
25
54
  this.headers = false;
55
+ /** @type {string | false} */
26
56
  this.contentType = false;
57
+ /** @type {string | false} */
58
+ this.charset = false;
59
+ /** @type {string | false} */
60
+ this.disposition = false;
61
+ /** @type {string | false} */
62
+ this.filename = false;
27
63
  this.flowed = false;
28
64
  this.delSp = false;
29
65
 
30
66
  this.config = config || {};
31
- this.libmime = new libmime.Libmime({ Iconv: this.config.Iconv });
67
+ this.libmime = new Libmime({ Iconv: this.config.Iconv });
32
68
 
33
- this.parentPartNumber = (parentNode && this.partNr) || [];
69
+ /** @type {PartNumber} */
70
+ this.parentPartNumber = [];
71
+ /** @type {PartNumber | false} */
34
72
  this.partNr = false; // resolved later
35
73
  this.childPartNumbers = 0;
74
+ this.rfc822 = false;
75
+ /** @type {boolean | undefined} */
76
+ this.messageNode = undefined;
36
77
  }
37
78
 
79
+ /**
80
+ * @param {PartNumberItem} [provided]
81
+ * @returns {PartNumber}
82
+ */
38
83
  getPartNr(provided) {
84
+ /** @type {PartNumber} */
85
+ let partNr = this.partNr || [];
39
86
  if (provided) {
40
- return []
41
- .concat(this.partNr || [])
42
- .filter(nr => !isNaN(nr))
43
- .concat(provided);
87
+ return partNr.filter(nr => !isNaN(Number(nr))).concat(provided);
44
88
  }
45
89
  let childPartNr = ++this.childPartNumbers;
46
- return []
47
- .concat(this.partNr || [])
48
- .filter(nr => !isNaN(nr))
49
- .concat(childPartNr);
90
+ return partNr.filter(nr => !isNaN(Number(nr))).concat(childPartNr);
50
91
  }
51
92
 
93
+ /**
94
+ * @param {Buffer | false} [line]
95
+ * @returns {void}
96
+ */
52
97
  addHeaderChunk(line) {
53
98
  if (!line) {
54
99
  return;
@@ -57,27 +102,32 @@ class MimeNode {
57
102
  this._headerlen += line.length;
58
103
  }
59
104
 
105
+ /**
106
+ * @returns {void}
107
+ */
60
108
  parseHeaders() {
61
109
  if (this.headers) {
62
110
  return;
63
111
  }
64
112
  this.headers = new Headers(Buffer.concat(this._headersLines, this._headerlen), this.config);
113
+ let headers = this.headers;
65
114
 
66
- this._parsedContentDisposition = this.libmime.parseHeaderValue(this.headers.getFirst('Content-Disposition'));
115
+ this._parsedContentDisposition = this.libmime.parseHeaderValue(headers.getFirst('Content-Disposition'));
116
+ let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
67
117
 
68
118
  // if content-type is missing default to plaintext
69
119
  let contentHeader;
70
- if (this.headers.get('Content-Type').length) {
71
- contentHeader = this.headers.getFirst('Content-Type');
120
+ if (headers.get('Content-Type').length) {
121
+ contentHeader = headers.getFirst('Content-Type');
72
122
  } else {
73
- if (this._parsedContentDisposition.params.filename) {
74
- let extension = pathlib.parse(this._parsedContentDisposition.params.filename).ext.replace(/^\./, '');
123
+ if (parsedContentDisposition.params.filename) {
124
+ let extension = pathlib.parse(parsedContentDisposition.params.filename).ext.replace(/^\./, '');
75
125
  if (extension) {
76
126
  contentHeader = libmime.detectMimeType(extension);
77
127
  }
78
128
  }
79
129
  if (!contentHeader) {
80
- if (/^attachment$/i.test(this._parsedContentDisposition.value)) {
130
+ if (/^attachment$/i.test(parsedContentDisposition.value)) {
81
131
  contentHeader = 'application/octet-stream';
82
132
  } else {
83
133
  contentHeader = 'text/plain';
@@ -86,15 +136,16 @@ class MimeNode {
86
136
  }
87
137
 
88
138
  this._parsedContentType = this.libmime.parseHeaderValue(contentHeader);
139
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
89
140
 
90
- this.encoding = this.headers
141
+ this.encoding = headers
91
142
  .getFirst('Content-Transfer-Encoding')
92
143
  .replace(/\(.*\)/g, '')
93
144
  .toLowerCase()
94
145
  .trim();
95
- this.contentType = (this._parsedContentType.value || '').toLowerCase().trim() || false;
96
- this.charset = this._parsedContentType.params.charset || false;
97
- this.disposition = (this._parsedContentDisposition.value || '').toLowerCase().trim() || false;
146
+ this.contentType = (parsedContentType.value || '').toLowerCase().trim() || false;
147
+ this.charset = parsedContentType.params.charset || false;
148
+ this.disposition = (parsedContentDisposition.value || '').toLowerCase().trim() || false;
98
149
 
99
150
  // fix invalidly encoded disposition values
100
151
  if (this.disposition) {
@@ -105,11 +156,11 @@ class MimeNode {
105
156
  }
106
157
  }
107
158
 
108
- this.filename = this._parsedContentDisposition.params.filename || this._parsedContentType.params.name || false;
159
+ this.filename = parsedContentDisposition.params.filename || parsedContentType.params.name || false;
109
160
 
110
- if (this._parsedContentType.params.format && this._parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
161
+ if (parsedContentType.params.format && parsedContentType.params.format.toLowerCase().trim() === 'flowed') {
111
162
  this.flowed = true;
112
- if (this._parsedContentType.params.delsp && this._parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
163
+ if (parsedContentType.params.delsp && parsedContentType.params.delsp.toLowerCase().trim() === 'yes') {
113
164
  this.delSp = true;
114
165
  }
115
166
  }
@@ -127,7 +178,7 @@ class MimeNode {
127
178
  this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' &&
128
179
  this.contentType.substr(this.contentType.indexOf('/') + 1)) ||
129
180
  false;
130
- this._boundary = (this._parsedContentType.params.boundary && Buffer.from(this._parsedContentType.params.boundary)) || false;
181
+ this._boundary = (parsedContentType.params.boundary && Buffer.from(parsedContentType.params.boundary)) || false;
131
182
 
132
183
  this.rfc822 = this.contentType === 'message/rfc822';
133
184
 
@@ -138,38 +189,54 @@ class MimeNode {
138
189
  }
139
190
  }
140
191
 
192
+ /**
193
+ * @returns {Buffer}
194
+ */
141
195
  getHeaders() {
142
196
  if (!this.headers) {
143
197
  this.parseHeaders();
144
198
  }
145
- return this.headers.build();
199
+ let headers = /** @type {HeadersType} */ (this.headers);
200
+ return headers.build();
146
201
  }
147
202
 
203
+ /**
204
+ * @param {string | false} [contentType]
205
+ * @returns {void}
206
+ */
148
207
  setContentType(contentType) {
149
208
  if (!this.headers) {
150
209
  this.parseHeaders();
151
210
  }
211
+ let headers = /** @type {HeadersType} */ (this.headers);
212
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
152
213
 
153
214
  contentType = (contentType || '').toLowerCase().trim();
154
215
  if (contentType) {
155
- this._parsedContentType.value = contentType;
216
+ parsedContentType.value = contentType;
156
217
  }
157
218
 
158
- if (!this.flowed && this._parsedContentType.params.format) {
159
- delete this._parsedContentType.params.format;
219
+ if (!this.flowed && parsedContentType.params.format) {
220
+ delete parsedContentType.params.format;
160
221
  }
161
222
 
162
- if (!this.delSp && this._parsedContentType.params.delsp) {
163
- delete this._parsedContentType.params.delsp;
223
+ if (!this.delSp && parsedContentType.params.delsp) {
224
+ delete parsedContentType.params.delsp;
164
225
  }
165
226
 
166
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
227
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
167
228
  }
168
229
 
230
+ /**
231
+ * @param {string | false} [charset]
232
+ * @returns {void}
233
+ */
169
234
  setCharset(charset) {
170
235
  if (!this.headers) {
171
236
  this.parseHeaders();
172
237
  }
238
+ let headers = /** @type {HeadersType} */ (this.headers);
239
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
173
240
 
174
241
  charset = (charset || '').toLowerCase().trim();
175
242
 
@@ -178,51 +245,61 @@ class MimeNode {
178
245
  }
179
246
 
180
247
  if (!charset) {
181
- if (!this._parsedContentType.value) {
248
+ if (!parsedContentType.value) {
182
249
  // nothing to set or update
183
250
  return;
184
251
  }
185
- delete this._parsedContentType.params.charset;
252
+ delete parsedContentType.params.charset;
186
253
  } else {
187
- this._parsedContentType.params.charset = charset;
254
+ parsedContentType.params.charset = charset;
188
255
  }
189
256
 
190
- if (!this._parsedContentType.value) {
191
- this._parsedContentType.value = 'text/plain';
257
+ if (!parsedContentType.value) {
258
+ parsedContentType.value = 'text/plain';
192
259
  }
193
260
 
194
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
261
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
195
262
  }
196
263
 
264
+ /**
265
+ * @param {string | false} [filename]
266
+ * @returns {void}
267
+ */
197
268
  setFilename(filename) {
198
269
  if (!this.headers) {
199
270
  this.parseHeaders();
200
271
  }
272
+ let headers = /** @type {HeadersType} */ (this.headers);
273
+ let parsedContentType = /** @type {StructuredHeader} */ (this._parsedContentType);
274
+ let parsedContentDisposition = /** @type {StructuredHeader} */ (this._parsedContentDisposition);
201
275
 
202
276
  this.filename = (filename || '').toLowerCase().trim();
203
277
 
204
- if (this._parsedContentType.params.name) {
205
- delete this._parsedContentType.params.name;
206
- this.headers.update('Content-Type', this.libmime.buildHeaderValue(this._parsedContentType));
278
+ if (parsedContentType.params.name) {
279
+ delete parsedContentType.params.name;
280
+ headers.update('Content-Type', this.libmime.buildHeaderValue(parsedContentType));
207
281
  }
208
282
 
209
283
  if (!this.filename) {
210
- if (!this._parsedContentDisposition.value) {
284
+ if (!parsedContentDisposition.value) {
211
285
  // nothing to set or update
212
286
  return;
213
287
  }
214
- delete this._parsedContentDisposition.params.filename;
288
+ delete parsedContentDisposition.params.filename;
215
289
  } else {
216
- this._parsedContentDisposition.params.filename = this.filename;
290
+ parsedContentDisposition.params.filename = this.filename;
217
291
  }
218
292
 
219
- if (!this._parsedContentDisposition.value) {
220
- this._parsedContentDisposition.value = 'attachment';
293
+ if (!parsedContentDisposition.value) {
294
+ parsedContentDisposition.value = 'attachment';
221
295
  }
222
296
 
223
- this.headers.update('Content-Disposition', this.libmime.buildHeaderValue(this._parsedContentDisposition));
297
+ headers.update('Content-Disposition', this.libmime.buildHeaderValue(parsedContentDisposition));
224
298
  }
225
299
 
300
+ /**
301
+ * @returns {import('stream').Transform | import('stream').PassThrough}
302
+ */
226
303
  getDecoder() {
227
304
  if (!this.headers) {
228
305
  this.parseHeaders();
@@ -238,15 +315,20 @@ class MimeNode {
238
315
  }
239
316
  }
240
317
 
318
+ /**
319
+ * @param {string | false} [encoding]
320
+ * @returns {import('stream').Transform | import('stream').PassThrough}
321
+ */
241
322
  getEncoder(encoding) {
242
323
  if (!this.headers) {
243
324
  this.parseHeaders();
244
325
  }
326
+ let headers = /** @type {HeadersType} */ (this.headers);
245
327
 
246
328
  encoding = (encoding || '').toString().toLowerCase().trim();
247
329
 
248
330
  if (encoding && encoding !== this.encoding) {
249
- this.headers.update('Content-Transfer-Encoding', encoding);
331
+ headers.update('Content-Transfer-Encoding', encoding);
250
332
  } else {
251
333
  encoding = this.encoding;
252
334
  }
@@ -0,0 +1,105 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { FilterFunc, RewriterNode, SplitterChunk } from './types';
3
+
4
+ /** Transform stream that replaces the body content of selected MIME nodes. */
5
+ declare class NodeRewriter extends Transform {
6
+ /**
7
+ * Creates a node rewriter that accepts splitter chunks and emits rewritten splitter chunks.
8
+ *
9
+ * @param filterFunc Predicate that selects MIME nodes to rewrite.
10
+ * @param rewriteAction Optional compatibility hook stored on the instance; consumers usually handle the `node` event.
11
+ */
12
+ constructor(filterFunc: FilterFunc, rewriteAction?: Function);
13
+
14
+ /**
15
+ * Registers a listener for rewritten splitter chunks.
16
+ *
17
+ * @param event Event name.
18
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
19
+ * @returns This rewriter instance.
20
+ */
21
+ on(event: 'data', listener: (data: SplitterChunk) => void): this;
22
+
23
+ /**
24
+ * Registers a listener for selected nodes.
25
+ *
26
+ * @param event Event name.
27
+ * @param listener Receives decoder and encoder streams for a selected node.
28
+ * @returns This rewriter instance.
29
+ */
30
+ on(event: 'node', listener: (data: RewriterNode) => void): this;
31
+
32
+ /**
33
+ * Registers a one-time listener for the next rewritten splitter chunk.
34
+ *
35
+ * @param event Event name.
36
+ * @param listener Receives the next outgoing MIME node, data chunk, or body chunk.
37
+ * @returns This rewriter instance.
38
+ */
39
+ once(event: 'data', listener: (data: SplitterChunk) => void): this;
40
+
41
+ /**
42
+ * Registers a one-time listener for the next selected node.
43
+ *
44
+ * @param event Event name.
45
+ * @param listener Receives decoder and encoder streams for the next selected node.
46
+ * @returns This rewriter instance.
47
+ */
48
+ once(event: 'node', listener: (data: RewriterNode) => void): this;
49
+
50
+ /**
51
+ * Adds a listener for rewritten splitter chunks.
52
+ *
53
+ * @param event Event name.
54
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
55
+ * @returns This rewriter instance.
56
+ */
57
+ addListener(event: 'data', listener: (data: SplitterChunk) => void): this;
58
+
59
+ /**
60
+ * Adds a listener for selected nodes.
61
+ *
62
+ * @param event Event name.
63
+ * @param listener Receives decoder and encoder streams for a selected node.
64
+ * @returns This rewriter instance.
65
+ */
66
+ addListener(event: 'node', listener: (data: RewriterNode) => void): this;
67
+
68
+ /**
69
+ * Prepends a listener for rewritten splitter chunks.
70
+ *
71
+ * @param event Event name.
72
+ * @param listener Receives each outgoing MIME node, data chunk, or body chunk.
73
+ * @returns This rewriter instance.
74
+ */
75
+ prependListener(event: 'data', listener: (data: SplitterChunk) => void): this;
76
+
77
+ /**
78
+ * Prepends a listener for selected nodes.
79
+ *
80
+ * @param event Event name.
81
+ * @param listener Receives decoder and encoder streams for a selected node.
82
+ * @returns This rewriter instance.
83
+ */
84
+ prependListener(event: 'node', listener: (data: RewriterNode) => void): this;
85
+
86
+ /**
87
+ * Emits a rewritten splitter chunk.
88
+ *
89
+ * @param event Event name.
90
+ * @param data MIME node, data chunk, or body chunk to emit.
91
+ * @returns `true` when the event had listeners.
92
+ */
93
+ emit(event: 'data', data: SplitterChunk): boolean;
94
+
95
+ /**
96
+ * Emits a selected-node payload.
97
+ *
98
+ * @param event Event name.
99
+ * @param data Selected node payload containing decoder and encoder streams.
100
+ * @returns `true` when the event had listeners.
101
+ */
102
+ emit(event: 'node', data: RewriterNode): boolean;
103
+ }
104
+
105
+ export = NodeRewriter;