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, Text, 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
243
fn lint_entry(entry: &Entry) -> Entry {
38
243
    let request = lint_request(&entry.request);
39
243
    let response = entry.response.as_ref().map(lint_response);
40
243
    Entry { request, response }
41
}
42

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

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

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

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

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

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

            
110
114
fn lint_section_value(section_value: &SectionValue) -> SectionValue {
111
114
    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
42
        SectionValue::Asserts(asserts) => {
122
42
            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
24
        SectionValue::Options(options) => {
135
24
            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
330
fn lint_assert(assert: &Assert) -> Assert {
156
330
    let filters = assert
157
330
        .filters
158
330
        .iter()
159
361
        .map(|(_, f)| (one_whitespace(), lint_filter(f)))
160
330
        .collect();
161
330
    Assert {
162
330
        line_terminators: assert.line_terminators.clone(),
163
330
        space0: empty_whitespace(),
164
330
        query: lint_query(&assert.query),
165
330
        filters,
166
330
        space1: one_whitespace(),
167
330
        predicate: lint_predicate(&assert.predicate),
168
330
        line_terminator0: assert.line_terminator0.clone(),
169
    }
170
}
171

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

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

            
204
342
fn lint_query_value(query_value: &QueryValue) -> QueryValue {
205
342
    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
30
        QueryValue::Body => QueryValue::Body,
227
3
        QueryValue::Xpath { expr, .. } => QueryValue::Xpath {
228
3
            expr: expr.clone(),
229
3
            space0: one_whitespace(),
230
3
        },
231
201
        QueryValue::Jsonpath { expr, .. } => QueryValue::Jsonpath {
232
201
            expr: expr.clone(),
233
201
            space0: one_whitespace(),
234
201
        },
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
21
        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
    }
256
}
257

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

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

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

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

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

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

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

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

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

            
390
36
fn lint_multiline_string(multiline_string: &MultilineString) -> MultilineString {
391
36
    match multiline_string {
392
        MultilineString {
393
18
            kind: MultilineStringKind::Text(value),
394
18
            attributes,
395
18
        } => MultilineString {
396
18
            kind: MultilineStringKind::Text(lint_text(value)),
397
18
            attributes: lint_multiline_string_attributes(attributes),
398
18
        },
399
        MultilineString {
400
6
            kind: MultilineStringKind::Json(value),
401
6
            attributes,
402
6
        } => MultilineString {
403
6
            kind: MultilineStringKind::Json(lint_text(value)),
404
6
            attributes: lint_multiline_string_attributes(attributes),
405
6
        },
406
        MultilineString {
407
6
            kind: MultilineStringKind::Xml(value),
408
6
            attributes,
409
6
        } => MultilineString {
410
6
            kind: MultilineStringKind::Xml(lint_text(value)),
411
6
            attributes: lint_multiline_string_attributes(attributes),
412
6
        },
413
        MultilineString {
414
6
            kind: MultilineStringKind::GraphQl(value),
415
6
            attributes,
416
6
        } => MultilineString {
417
6
            kind: MultilineStringKind::GraphQl(lint_graphql(value)),
418
6
            attributes: lint_multiline_string_attributes(attributes),
419
6
        },
420
    }
421
}
422

            
423
36
fn lint_multiline_string_attributes(
424
36
    attributes: &[MultilineStringAttribute],
425
36
) -> Vec<MultilineStringAttribute> {
426
36
    attributes.to_vec()
427
}
428

            
429
30
fn lint_text(text: &Text) -> Text {
430
30
    let space = empty_whitespace();
431
30
    let newline = text.newline.clone();
432
30
    let value = lint_template(&text.value);
433
30
    Text {
434
30
        space,
435
30
        newline,
436
30
        value,
437
    }
438
}
439

            
440
6
fn lint_graphql(graphql: &GraphQl) -> GraphQl {
441
6
    let space = empty_whitespace();
442
6
    let newline = graphql.newline.clone();
443
6
    let value = lint_template(&graphql.value);
444
6
    let variables = graphql.variables.clone();
445
6
    GraphQl {
446
6
        space,
447
6
        newline,
448
6
        value,
449
6
        variables,
450
    }
451
}
452

            
453
9
fn lint_cookie(cookie: &Cookie) -> Cookie {
454
9
    cookie.clone()
455
}
456

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

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

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

            
491
27
fn lint_hex(hex: &Hex) -> Hex {
492
27
    Hex {
493
27
        space0: empty_whitespace(),
494
27
        value: hex.value.clone(),
495
27
        source: hex.source.clone(),
496
27
        space1: empty_whitespace(),
497
    }
498
}
499

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

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

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

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

            
552
2442
fn empty_whitespace() -> Whitespace {
553
2442
    Whitespace {
554
2442
        value: String::new(),
555
2442
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
556
    }
557
}
558

            
559
1626
fn one_whitespace() -> Whitespace {
560
1626
    Whitespace {
561
1626
        value: " ".to_string(),
562
1626
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
563
    }
564
}
565

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

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

            
601
165
fn lint_template(template: &Template) -> Template {
602
165
    template.clone()
603
}
604

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

            
616
273
fn lint_option_kind(option_kind: &OptionKind) -> OptionKind {
617
273
    match option_kind {
618
12
        OptionKind::Delay(duration) => {
619
12
            OptionKind::Delay(lint_duration_option(duration, DurationUnit::MilliSecond))
620
        }
621
12
        OptionKind::RetryInterval(duration) => {
622
12
            OptionKind::RetryInterval(lint_duration_option(duration, DurationUnit::MilliSecond))
623
        }
624
27
        OptionKind::Variable(var_def) => OptionKind::Variable(lint_variable_definition(var_def)),
625
222
        _ => option_kind.clone(),
626
    }
627
}
628

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

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

            
647
96
fn lint_filter(filter: &Filter) -> Filter {
648
96
    Filter {
649
96
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
650
96
        value: lint_filter_value(&filter.value),
651
    }
652
}
653

            
654
96
fn lint_filter_value(filter_value: &FilterValue) -> FilterValue {
655
96
    match filter_value {
656
3
        FilterValue::Regex { value, .. } => FilterValue::Regex {
657
3
            space0: one_whitespace(),
658
3
            value: lint_regex_value(value),
659
3
        },
660
93
        f => f.clone(),
661
    }
662
}
663

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

            
672
#[cfg(test)]
673
mod tests {
674
    use super::*;
675

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

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