1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2025 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use hurl_core::ast::{
19
    Assert, Base64, Body, Bytes, Capture, Comment, Cookie, CookieAttribute, CookieAttributeName,
20
    CookiePath, DurationOption, Entry, EntryOption, File, FileParam, Filter, FilterValue, GraphQl,
21
    Hex, HurlFile, KeyValue, LineTerminator, MultilineString, MultilineStringAttribute,
22
    MultilineStringKind, MultipartParam, OptionKind, Predicate, PredicateFunc, PredicateFuncValue,
23
    PredicateValue, Query, QueryValue, RegexValue, Request, Response, Section, SectionValue,
24
    SourceInfo, Template, VariableDefinition, Whitespace,
25
};
26
use hurl_core::reader::Pos;
27
use hurl_core::typing::{Duration, DurationUnit};
28

            
29
/// Returns a new linted instance from this `hurl_file`.
30
81
pub fn lint_hurl_file(hurl_file: &HurlFile) -> HurlFile {
31
81
    HurlFile {
32
81
        entries: hurl_file.entries.iter().map(lint_entry).collect(),
33
81
        line_terminators: hurl_file.line_terminators.clone(),
34
    }
35
}
36

            
37
264
fn lint_entry(entry: &Entry) -> Entry {
38
264
    let request = lint_request(&entry.request);
39
264
    let response = entry.response.as_ref().map(lint_response);
40
264
    Entry { request, response }
41
}
42

            
43
264
fn lint_request(request: &Request) -> Request {
44
264
    let line_terminators = request.line_terminators.clone();
45
264
    let space0 = empty_whitespace();
46
264
    let method = request.method.clone();
47
264
    let space1 = one_whitespace();
48
264

            
49
264
    let url = request.url.clone();
50
264
    let line_terminator0 = lint_line_terminator(&request.line_terminator0);
51
264
    let headers = request.headers.iter().map(lint_key_value).collect();
52
264
    let body = request.body.as_ref().map(lint_body);
53
264
    let mut sections: Vec<Section> = request.sections.iter().map(lint_section).collect();
54
298
    sections.sort_by_key(|k| section_value_index(k.value.clone()));
55
264

            
56
264
    let source_info = SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0));
57
264
    Request {
58
264
        line_terminators,
59
264
        space0,
60
264
        method,
61
264
        space1,
62
264
        url,
63
264
        line_terminator0,
64
264
        headers,
65
264
        sections,
66
264
        body,
67
264
        source_info,
68
    }
69
}
70

            
71
108
fn lint_response(response: &Response) -> Response {
72
108
    let line_terminators = response.line_terminators.clone();
73
108
    let space0 = empty_whitespace();
74
108
    let version = response.version.clone();
75
108
    let space1 = response.space1.clone();
76
108
    let status = response.status.clone();
77
108
    let line_terminator0 = response.line_terminator0.clone();
78
108
    let headers = response.headers.iter().map(lint_key_value).collect();
79
108
    let mut sections: Vec<Section> = response.sections.iter().map(lint_section).collect();
80
114
    sections.sort_by_key(|k| section_value_index(k.value.clone()));
81
108
    let body = response.body.clone();
82
108

            
83
108
    Response {
84
108
        line_terminators,
85
108
        space0,
86
108
        version,
87
108
        space1,
88
108
        status,
89
108
        line_terminator0,
90
108
        headers,
91
108
        sections,
92
108
        body,
93
108
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
94
    }
95
}
96

            
97
123
fn lint_section(section: &Section) -> Section {
98
123
    let line_terminators = section.line_terminators.clone();
99
123
    let line_terminator0 = section.line_terminator0.clone();
100
123
    let value = lint_section_value(&section.value);
101
123
    Section {
102
123
        line_terminators,
103
123
        space0: empty_whitespace(),
104
123
        value,
105
123
        line_terminator0,
106
123
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
107
    }
108
}
109

            
110
123
fn lint_section_value(section_value: &SectionValue) -> SectionValue {
111
123
    match section_value {
112
12
        SectionValue::QueryParams(params, short) => {
113
12
            SectionValue::QueryParams(params.iter().map(lint_key_value).collect(), *short)
114
        }
115
3
        SectionValue::BasicAuth(param) => {
116
3
            SectionValue::BasicAuth(param.as_ref().map(lint_key_value))
117
        }
118
12
        SectionValue::Captures(captures) => {
119
12
            SectionValue::Captures(captures.iter().map(lint_capture).collect())
120
        }
121
45
        SectionValue::Asserts(asserts) => {
122
45
            SectionValue::Asserts(asserts.iter().map(lint_assert).collect())
123
        }
124
6
        SectionValue::FormParams(params, short) => {
125
6
            SectionValue::FormParams(params.iter().map(lint_key_value).collect(), *short)
126
        }
127
6
        SectionValue::MultipartFormData(params, short) => SectionValue::MultipartFormData(
128
6
            params.iter().map(lint_multipart_param).collect(),
129
6
            *short,
130
6
        ),
131
9
        SectionValue::Cookies(cookies) => {
132
9
            SectionValue::Cookies(cookies.iter().map(lint_cookie).collect())
133
        }
134
30
        SectionValue::Options(options) => {
135
30
            SectionValue::Options(options.iter().map(lint_entry_option).collect())
136
        }
137
    }
138
}
139

            
140
120
fn section_value_index(section_value: SectionValue) -> u32 {
141
120
    match section_value {
142
        // Request sections
143
27
        SectionValue::Options(_) => 0,
144
15
        SectionValue::QueryParams(_, _) => 1,
145
9
        SectionValue::BasicAuth(_) => 2,
146
18
        SectionValue::FormParams(_, _) => 3,
147
18
        SectionValue::MultipartFormData(_, _) => 4,
148
15
        SectionValue::Cookies(_) => 5,
149
        // Response sections
150
9
        SectionValue::Captures(_) => 0,
151
9
        SectionValue::Asserts(_) => 1,
152
    }
153
}
154

            
155
345
fn lint_assert(assert: &Assert) -> Assert {
156
345
    let filters = assert
157
345
        .filters
158
345
        .iter()
159
380
        .map(|(_, f)| (one_whitespace(), lint_filter(f)))
160
345
        .collect();
161
345
    Assert {
162
345
        line_terminators: assert.line_terminators.clone(),
163
345
        space0: empty_whitespace(),
164
345
        query: lint_query(&assert.query),
165
345
        filters,
166
345
        space1: one_whitespace(),
167
345
        predicate: lint_predicate(&assert.predicate),
168
345
        line_terminator0: assert.line_terminator0.clone(),
169
    }
170
}
171

            
172
18
fn lint_capture(capture: &Capture) -> Capture {
173
18
    let filters = capture
174
18
        .filters
175
18
        .iter()
176
19
        .map(|(_, f)| (one_whitespace(), lint_filter(f)))
177
18
        .collect();
178
18
    let space3 = if capture.redact {
179
6
        one_whitespace()
180
    } else {
181
12
        capture.space3.clone()
182
    };
183
18
    Capture {
184
18
        line_terminators: capture.line_terminators.clone(),
185
18
        space0: empty_whitespace(),
186
18
        name: capture.name.clone(),
187
18
        space1: empty_whitespace(),
188
18
        space2: one_whitespace(),
189
18
        query: lint_query(&capture.query),
190
18
        filters,
191
18
        space3,
192
18
        redact: capture.redact,
193
18
        line_terminator0: lint_line_terminator(&capture.line_terminator0),
194
    }
195
}
196

            
197
363
fn lint_query(query: &Query) -> Query {
198
363
    Query {
199
363
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
200
363
        value: lint_query_value(&query.value),
201
    }
202
}
203

            
204
363
fn lint_query_value(query_value: &QueryValue) -> QueryValue {
205
363
    match query_value {
206
6
        QueryValue::Status => QueryValue::Status,
207
3
        QueryValue::Version => QueryValue::Version,
208
3
        QueryValue::Url => QueryValue::Url,
209
12
        QueryValue::Header { name, .. } => QueryValue::Header {
210
12
            name: name.clone(),
211
12
            space0: one_whitespace(),
212
12
        },
213
        QueryValue::Cookie {
214
6
            expr: CookiePath { name, attribute },
215
6
            ..
216
6
        } => {
217
6
            let attribute = attribute.as_ref().map(lint_cookie_attribute);
218
6
            QueryValue::Cookie {
219
6
                space0: one_whitespace(),
220
6
                expr: CookiePath {
221
6
                    name: name.clone(),
222
6
                    attribute,
223
6
                },
224
            }
225
        }
226
33
        QueryValue::Body => QueryValue::Body,
227
3
        QueryValue::Xpath { expr, .. } => QueryValue::Xpath {
228
3
            expr: expr.clone(),
229
3
            space0: one_whitespace(),
230
3
        },
231
216
        QueryValue::Jsonpath { expr, .. } => QueryValue::Jsonpath {
232
216
            expr: expr.clone(),
233
216
            space0: one_whitespace(),
234
216
        },
235
3
        QueryValue::Regex { value, .. } => QueryValue::Regex {
236
3
            value: lint_regex_value(value),
237
3
            space0: one_whitespace(),
238
3
        },
239
9
        QueryValue::Variable { name, .. } => QueryValue::Variable {
240
9
            name: name.clone(),
241
9
            space0: one_whitespace(),
242
9
        },
243
3
        QueryValue::Duration => QueryValue::Duration,
244
24
        QueryValue::Bytes => QueryValue::Bytes,
245
3
        QueryValue::Sha256 => QueryValue::Sha256,
246
3
        QueryValue::Md5 => QueryValue::Md5,
247
        QueryValue::Certificate {
248
30
            attribute_name: field,
249
30
            ..
250
30
        } => QueryValue::Certificate {
251
30
            attribute_name: *field,
252
30
            space0: one_whitespace(),
253
30
        },
254
6
        QueryValue::Ip => QueryValue::Ip,
255
        QueryValue::Redirects => QueryValue::Redirects,
256
    }
257
}
258

            
259
6
fn lint_regex_value(regex_value: &RegexValue) -> RegexValue {
260
6
    match regex_value {
261
3
        RegexValue::Template(template) => RegexValue::Template(lint_template(template)),
262
3
        RegexValue::Regex(regex) => RegexValue::Regex(regex.clone()),
263
    }
264
}
265

            
266
3
fn lint_cookie_attribute(cookie_attribute: &CookieAttribute) -> CookieAttribute {
267
3
    let space0 = empty_whitespace();
268
3
    let name = lint_cookie_attribute_name(&cookie_attribute.name);
269
3
    let space1 = empty_whitespace();
270
3
    CookieAttribute {
271
3
        space0,
272
3
        name,
273
3
        space1,
274
    }
275
}
276

            
277
3
fn lint_cookie_attribute_name(cookie_attribute_name: &CookieAttributeName) -> CookieAttributeName {
278
3
    match cookie_attribute_name {
279
        CookieAttributeName::Value(_) => CookieAttributeName::Value("Value".to_string()),
280
3
        CookieAttributeName::Expires(_) => CookieAttributeName::Expires("Expires".to_string()),
281
        CookieAttributeName::MaxAge(_) => CookieAttributeName::MaxAge("Max-Age".to_string()),
282
        CookieAttributeName::Domain(_) => CookieAttributeName::Domain("Domain".to_string()),
283
        CookieAttributeName::Path(_) => CookieAttributeName::Path("Path".to_string()),
284
        CookieAttributeName::Secure(_) => CookieAttributeName::Secure("Secure".to_string()),
285
        CookieAttributeName::HttpOnly(_) => CookieAttributeName::HttpOnly("HttpOnly".to_string()),
286
        CookieAttributeName::SameSite(_) => CookieAttributeName::SameSite("SameSite".to_string()),
287
    }
288
}
289

            
290
345
fn lint_predicate(predicate: &Predicate) -> Predicate {
291
345
    Predicate {
292
345
        not: predicate.not,
293
345
        space0: if predicate.not {
294
3
            one_whitespace()
295
        } else {
296
342
            empty_whitespace()
297
        },
298
345
        predicate_func: lint_predicate_func(&predicate.predicate_func),
299
    }
300
}
301

            
302
345
fn lint_predicate_func(predicate_func: &PredicateFunc) -> PredicateFunc {
303
345
    PredicateFunc {
304
345
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
305
345
        value: lint_predicate_func_value(&predicate_func.value),
306
    }
307
}
308

            
309
345
fn lint_predicate_func_value(predicate_func_value: &PredicateFuncValue) -> PredicateFuncValue {
310
345
    match predicate_func_value {
311
225
        PredicateFuncValue::Equal { value, .. } => PredicateFuncValue::Equal {
312
225
            space0: one_whitespace(),
313
225
            value: lint_predicate_value(value),
314
225
        },
315
9
        PredicateFuncValue::NotEqual { value, .. } => PredicateFuncValue::NotEqual {
316
9
            space0: one_whitespace(),
317
9
            value: lint_predicate_value(value),
318
9
        },
319
9
        PredicateFuncValue::GreaterThan { value, .. } => PredicateFuncValue::GreaterThan {
320
9
            space0: one_whitespace(),
321
9
            value: lint_predicate_value(value),
322
9
        },
323
3
        PredicateFuncValue::GreaterThanOrEqual { value, .. } => {
324
3
            PredicateFuncValue::GreaterThanOrEqual {
325
3
                space0: one_whitespace(),
326
3
                value: lint_predicate_value(value),
327
            }
328
        }
329
12
        PredicateFuncValue::LessThan { value, .. } => PredicateFuncValue::LessThan {
330
12
            space0: one_whitespace(),
331
12
            value: lint_predicate_value(value),
332
12
        },
333
3
        PredicateFuncValue::LessThanOrEqual { value, .. } => PredicateFuncValue::LessThanOrEqual {
334
3
            space0: one_whitespace(),
335
3
            value: lint_predicate_value(value),
336
3
        },
337
9
        PredicateFuncValue::Contain { value, .. } => PredicateFuncValue::Contain {
338
9
            space0: one_whitespace(),
339
9
            value: lint_predicate_value(value),
340
9
        },
341

            
342
3
        PredicateFuncValue::Include { value, .. } => PredicateFuncValue::Include {
343
3
            space0: one_whitespace(),
344
3
            value: lint_predicate_value(value),
345
3
        },
346

            
347
6
        PredicateFuncValue::Match { value, .. } => PredicateFuncValue::Match {
348
6
            space0: one_whitespace(),
349
6
            value: lint_predicate_value(value),
350
6
        },
351
9
        PredicateFuncValue::StartWith { value, .. } => PredicateFuncValue::StartWith {
352
9
            space0: one_whitespace(),
353
9
            value: lint_predicate_value(value),
354
9
        },
355
6
        PredicateFuncValue::EndWith { value, .. } => PredicateFuncValue::EndWith {
356
6
            space0: one_whitespace(),
357
6
            value: lint_predicate_value(value),
358
6
        },
359
3
        PredicateFuncValue::IsInteger => PredicateFuncValue::IsInteger,
360
3
        PredicateFuncValue::IsFloat => PredicateFuncValue::IsFloat,
361
3
        PredicateFuncValue::IsBoolean => PredicateFuncValue::IsBoolean,
362
3
        PredicateFuncValue::IsString => PredicateFuncValue::IsString,
363
3
        PredicateFuncValue::IsCollection => PredicateFuncValue::IsCollection,
364
9
        PredicateFuncValue::IsDate => PredicateFuncValue::IsDate,
365
3
        PredicateFuncValue::IsIsoDate => PredicateFuncValue::IsIsoDate,
366
12
        PredicateFuncValue::Exist => PredicateFuncValue::Exist,
367
3
        PredicateFuncValue::IsEmpty => PredicateFuncValue::IsEmpty,
368
3
        PredicateFuncValue::IsNumber => PredicateFuncValue::IsNumber,
369
3
        PredicateFuncValue::IsIpv4 => PredicateFuncValue::IsIpv4,
370
3
        PredicateFuncValue::IsIpv6 => PredicateFuncValue::IsIpv6,
371
    }
372
}
373

            
374
294
fn lint_predicate_value(predicate_value: &PredicateValue) -> PredicateValue {
375
294
    match predicate_value {
376
123
        PredicateValue::String(value) => PredicateValue::String(lint_template(value)),
377
18
        PredicateValue::MultilineString(value) => {
378
18
            PredicateValue::MultilineString(lint_multiline_string(value))
379
        }
380
3
        PredicateValue::Bool(value) => PredicateValue::Bool(*value),
381
3
        PredicateValue::Null => PredicateValue::Null,
382
108
        PredicateValue::Number(value) => PredicateValue::Number(value.clone()),
383
3
        PredicateValue::File(value) => PredicateValue::File(lint_file(value)),
384
27
        PredicateValue::Hex(value) => PredicateValue::Hex(lint_hex(value)),
385
3
        PredicateValue::Base64(value) => PredicateValue::Base64(lint_base64(value)),
386
3
        PredicateValue::Placeholder(value) => PredicateValue::Placeholder(value.clone()),
387
3
        PredicateValue::Regex(value) => PredicateValue::Regex(value.clone()),
388
    }
389
}
390

            
391
45
fn lint_multiline_string(multiline_string: &MultilineString) -> MultilineString {
392
45
    let space = empty_whitespace();
393
45
    let newline = multiline_string.newline.clone();
394
45
    match multiline_string {
395
        MultilineString {
396
24
            attributes,
397
24
            kind: MultilineStringKind::Text(value),
398
24
            ..
399
24
        } => MultilineString {
400
24
            attributes: lint_multiline_string_attributes(attributes),
401
24
            space,
402
24
            newline,
403
24
            kind: MultilineStringKind::Text(lint_template(value)),
404
24
        },
405
        MultilineString {
406
9
            attributes,
407
9
            kind: MultilineStringKind::Json(value),
408
9
            ..
409
9
        } => MultilineString {
410
9
            attributes: lint_multiline_string_attributes(attributes),
411
9
            space,
412
9
            newline,
413
9
            kind: MultilineStringKind::Json(lint_template(value)),
414
9
        },
415
        MultilineString {
416
6
            attributes,
417
6
            kind: MultilineStringKind::Xml(value),
418
6
            ..
419
6
        } => MultilineString {
420
6
            attributes: lint_multiline_string_attributes(attributes),
421
6
            space,
422
6
            newline,
423
6
            kind: MultilineStringKind::Xml(lint_template(value)),
424
6
        },
425
        MultilineString {
426
6
            attributes,
427
6
            kind: MultilineStringKind::GraphQl(value),
428
6
            ..
429
6
        } => MultilineString {
430
6
            attributes: lint_multiline_string_attributes(attributes),
431
6
            space,
432
6
            newline,
433
6
            kind: MultilineStringKind::GraphQl(lint_graphql(value)),
434
6
        },
435
    }
436
}
437

            
438
45
fn lint_multiline_string_attributes(
439
45
    attributes: &[MultilineStringAttribute],
440
45
) -> Vec<MultilineStringAttribute> {
441
45
    attributes.to_vec()
442
}
443

            
444
6
fn lint_graphql(graphql: &GraphQl) -> GraphQl {
445
6
    let value = lint_template(&graphql.value);
446
6
    let variables = graphql.variables.clone();
447
6
    GraphQl { value, variables }
448
}
449

            
450
9
fn lint_cookie(cookie: &Cookie) -> Cookie {
451
9
    cookie.clone()
452
}
453

            
454
51
fn lint_body(body: &Body) -> Body {
455
51
    let line_terminators = body.line_terminators.clone();
456
51
    let space0 = empty_whitespace();
457
51
    let value = lint_bytes(&body.value);
458
51
    let line_terminator0 = body.line_terminator0.clone();
459
51
    Body {
460
51
        line_terminators,
461
51
        space0,
462
51
        value,
463
51
        line_terminator0,
464
    }
465
}
466

            
467
51
fn lint_bytes(bytes: &Bytes) -> Bytes {
468
51
    match bytes {
469
6
        Bytes::File(value) => Bytes::File(lint_file(value)),
470
6
        Bytes::Base64(value) => Bytes::Base64(lint_base64(value)),
471
3
        Bytes::Hex(value) => Bytes::Hex(lint_hex(value)),
472
3
        Bytes::Json(value) => Bytes::Json(value.clone()),
473
3
        Bytes::OnelineString(value) => Bytes::OnelineString(lint_template(value)),
474
27
        Bytes::MultilineString(value) => Bytes::MultilineString(lint_multiline_string(value)),
475
3
        Bytes::Xml(value) => Bytes::Xml(value.clone()),
476
    }
477
}
478

            
479
9
fn lint_base64(base64: &Base64) -> Base64 {
480
9
    Base64 {
481
9
        space0: empty_whitespace(),
482
9
        value: base64.value.clone(),
483
9
        source: base64.source.clone(),
484
9
        space1: empty_whitespace(),
485
    }
486
}
487

            
488
30
fn lint_hex(hex: &Hex) -> Hex {
489
30
    Hex {
490
30
        space0: empty_whitespace(),
491
30
        value: hex.value.clone(),
492
30
        source: hex.source.clone(),
493
30
        space1: empty_whitespace(),
494
    }
495
}
496

            
497
9
fn lint_file(file: &File) -> File {
498
9
    File {
499
9
        space0: empty_whitespace(),
500
9
        filename: lint_template(&file.filename),
501
9
        space1: empty_whitespace(),
502
    }
503
}
504

            
505
126
fn lint_key_value(key_value: &KeyValue) -> KeyValue {
506
126
    KeyValue {
507
126
        line_terminators: key_value.line_terminators.clone(),
508
126
        space0: empty_whitespace(),
509
126
        key: key_value.key.clone(),
510
126
        space1: empty_whitespace(),
511
126
        space2: if key_value.value.elements.is_empty() {
512
6
            empty_whitespace()
513
        } else {
514
120
            one_whitespace()
515
        },
516
126
        value: key_value.value.clone(),
517
126
        line_terminator0: key_value.line_terminator0.clone(),
518
    }
519
}
520

            
521
9
fn lint_multipart_param(multipart_param: &MultipartParam) -> MultipartParam {
522
9
    match multipart_param {
523
3
        MultipartParam::Param(param) => MultipartParam::Param(lint_key_value(param)),
524
6
        MultipartParam::FileParam(file_param) => {
525
6
            MultipartParam::FileParam(lint_file_param(file_param))
526
        }
527
    }
528
}
529

            
530
6
fn lint_file_param(file_param: &FileParam) -> FileParam {
531
6
    let line_terminators = file_param.line_terminators.clone();
532
6
    let space0 = file_param.space0.clone();
533
6
    let key = file_param.key.clone();
534
6
    let space1 = file_param.space1.clone();
535
6
    let space2 = file_param.space2.clone();
536
6
    let value = file_param.value.clone();
537
6
    let line_terminator0 = file_param.line_terminator0.clone();
538
6
    FileParam {
539
6
        line_terminators,
540
6
        space0,
541
6
        key,
542
6
        space1,
543
6
        space2,
544
6
        value,
545
6
        line_terminator0,
546
    }
547
}
548

            
549
2562
fn empty_whitespace() -> Whitespace {
550
2562
    Whitespace {
551
2562
        value: String::new(),
552
2562
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
553
    }
554
}
555

            
556
1719
fn one_whitespace() -> Whitespace {
557
1719
    Whitespace {
558
1719
        value: " ".to_string(),
559
1719
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
560
    }
561
}
562

            
563
282
fn lint_line_terminator(line_terminator: &LineTerminator) -> LineTerminator {
564
282
    let space0 = match line_terminator.comment {
565
276
        None => empty_whitespace(),
566
6
        Some(_) => Whitespace {
567
6
            value: line_terminator.space0.value.clone(),
568
6
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
569
6
        },
570
    };
571
282
    let comment = line_terminator.comment.as_ref().map(lint_comment);
572
282
    let newline = Whitespace {
573
282
        value: if line_terminator.newline.value.is_empty() {
574
            String::new()
575
        } else {
576
282
            "\n".to_string()
577
        },
578
282
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
579
282
    };
580
282
    LineTerminator {
581
282
        space0,
582
282
        comment,
583
282
        newline,
584
    }
585
}
586

            
587
6
fn lint_comment(comment: &Comment) -> Comment {
588
6
    Comment {
589
6
        value: if comment.value.starts_with(' ') {
590
6
            comment.value.clone()
591
        } else {
592
            format!(" {}", comment.value)
593
        },
594
6
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
595
    }
596
}
597

            
598
183
fn lint_template(template: &Template) -> Template {
599
183
    template.clone()
600
}
601

            
602
279
fn lint_entry_option(entry_option: &EntryOption) -> EntryOption {
603
279
    EntryOption {
604
279
        line_terminators: entry_option.line_terminators.clone(),
605
279
        space0: empty_whitespace(),
606
279
        space1: empty_whitespace(),
607
279
        space2: one_whitespace(),
608
279
        kind: lint_option_kind(&entry_option.kind),
609
279
        line_terminator0: entry_option.line_terminator0.clone(),
610
    }
611
}
612

            
613
279
fn lint_option_kind(option_kind: &OptionKind) -> OptionKind {
614
279
    match option_kind {
615
12
        OptionKind::Delay(duration) => {
616
12
            OptionKind::Delay(lint_duration_option(duration, DurationUnit::MilliSecond))
617
        }
618
12
        OptionKind::RetryInterval(duration) => {
619
12
            OptionKind::RetryInterval(lint_duration_option(duration, DurationUnit::MilliSecond))
620
        }
621
27
        OptionKind::Variable(var_def) => OptionKind::Variable(lint_variable_definition(var_def)),
622
228
        _ => option_kind.clone(),
623
    }
624
}
625

            
626
24
fn lint_duration_option(
627
24
    duration_option: &DurationOption,
628
24
    default_unit: DurationUnit,
629
24
) -> DurationOption {
630
24
    match duration_option {
631
18
        DurationOption::Literal(duration) => {
632
18
            DurationOption::Literal(lint_duration(duration, default_unit))
633
        }
634
6
        DurationOption::Placeholder(expr) => DurationOption::Placeholder(expr.clone()),
635
    }
636
}
637

            
638
18
fn lint_duration(duration: &Duration, default_unit: DurationUnit) -> Duration {
639
18
    let value = duration.value.clone();
640
18
    let unit = Some(duration.unit.unwrap_or(default_unit));
641
18
    Duration { value, unit }
642
}
643

            
644
108
fn lint_filter(filter: &Filter) -> Filter {
645
108
    Filter {
646
108
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
647
108
        value: lint_filter_value(&filter.value),
648
    }
649
}
650

            
651
108
fn lint_filter_value(filter_value: &FilterValue) -> FilterValue {
652
108
    match filter_value {
653
3
        FilterValue::Regex { value, .. } => FilterValue::Regex {
654
3
            space0: one_whitespace(),
655
3
            value: lint_regex_value(value),
656
3
        },
657
105
        f => f.clone(),
658
    }
659
}
660

            
661
27
fn lint_variable_definition(var_def: &VariableDefinition) -> VariableDefinition {
662
27
    VariableDefinition {
663
27
        space0: empty_whitespace(),
664
27
        space1: empty_whitespace(),
665
27
        ..var_def.clone()
666
    }
667
}
668

            
669
#[cfg(test)]
670
mod tests {
671
    use super::*;
672

            
673
    #[test]
674
    fn test_hurl_file() {
675
        let hurl_file = HurlFile {
676
            entries: vec![],
677
            line_terminators: vec![],
678
        };
679
        let hurl_file_linted = HurlFile {
680
            entries: vec![],
681
            line_terminators: vec![],
682
        };
683
        assert_eq!(lint_hurl_file(&hurl_file), hurl_file_linted);
684
    }
685

            
686
    #[test]
687
    fn test_entry() {
688
        let entry = HurlFile {
689
            entries: vec![],
690
            line_terminators: vec![],
691
        };
692
        let entry_linted = HurlFile {
693
            entries: vec![],
694
            line_terminators: vec![],
695
        };
696
        assert_eq!(lint_hurl_file(&entry), entry_linted);
697
    }
698
}