1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Style and lossless tree utilities.

use crate::rule_prelude::*;
use rslint_parser::NodeOrToken;
use std::iter;
use SyntaxKind::*;

/// Extensions to nodes and tokens used for stylistic linting which cares about trivia (whitespace)
pub trait StyleExt {
    fn to_elem(&self) -> SyntaxElement;
    fn trailing_trivia_has_linebreak(&self, stop_at_comment: bool) -> bool {
        self.trailing_whitespace(stop_at_comment)
            .into_iter()
            .any(|x| parseutil::contains_js_linebreak(x.text().as_str()))
    }

    fn leading_trivia_has_linebreak(&self, stop_at_comment: bool) -> bool {
        self.leading_whitespace(stop_at_comment)
            .into_iter()
            .any(|x| parseutil::contains_js_linebreak(x.text().as_str()))
    }

    fn trailing_trivia(&self, stop_at_comment: bool) -> Vec<SyntaxToken> {
        let elem = self.to_elem();
        let token = elem
            .clone()
            .into_token()
            .or_else(|| elem.into_node().unwrap().first_token());

        iter::successors(
            token.and_then(|x| {
                if x.kind() == WHITESPACE || (x.kind() == COMMENT && !stop_at_comment) {
                    Some(x)
                } else {
                    x.next_token()
                }
            }),
            |next| next.next_token(),
        )
        .take_while(|x| x.kind().is_trivia())
        .collect()
    }

    fn trailing_whitespace(&self, stop_at_comment: bool) -> Vec<SyntaxToken> {
        self.trailing_trivia(stop_at_comment)
            .into_iter()
            .take_while(|x| x.kind() == WHITESPACE)
            .collect()
    }

    fn leading_trivia(&self, stop_at_comment: bool) -> Vec<SyntaxToken> {
        let elem = self.to_elem();
        let token = elem
            .clone()
            .into_token()
            .or_else(|| match elem.prev_sibling_or_token() {
                Some(elem) => match elem {
                    NodeOrToken::Node(n) => n.last_token(),
                    NodeOrToken::Token(t) => Some(t),
                },
                None => elem
                    .ancestors()
                    .find_map(|it| it.prev_sibling_or_token())
                    .and_then(|e| match e {
                        NodeOrToken::Node(n) => n.last_token(),
                        NodeOrToken::Token(t) => Some(t),
                    }),
            });

        iter::successors(
            token.and_then(|x| {
                if x.kind() == WHITESPACE || (x.kind() == COMMENT && !stop_at_comment) {
                    Some(x)
                } else {
                    x.prev_token()
                }
            }),
            |next| next.prev_token(),
        )
        .take_while(|x| x.kind().is_trivia())
        .collect()
    }

    fn leading_whitespace(&self, stop_at_comment: bool) -> Vec<SyntaxToken> {
        self.leading_trivia(stop_at_comment)
            .into_iter()
            .take_while(|x| x.kind() == WHITESPACE)
            .collect()
    }

    fn has_leading_whitespace(&self, include_linebreaks: bool, stop_at_comment: bool) -> bool {
        let leading = self.leading_whitespace(stop_at_comment);
        tokens_have_whitespace(&leading, include_linebreaks)
    }

    fn has_trailing_whitespace(&self, include_linebreaks: bool, stop_at_comment: bool) -> bool {
        let trailing = self.trailing_whitespace(stop_at_comment);
        tokens_have_whitespace(&trailing, include_linebreaks)
    }
}

impl StyleExt for SyntaxNode {
    fn to_elem(&self) -> SyntaxElement {
        SyntaxElement::from(self.to_owned())
    }
}

impl StyleExt for SyntaxToken {
    fn to_elem(&self) -> SyntaxElement {
        SyntaxElement::from(self.to_owned())
    }
}

impl StyleExt for SyntaxElement {
    fn to_elem(&self) -> SyntaxElement {
        self.to_owned()
    }
}

pub fn tokens_have_whitespace(tokens: &[SyntaxToken], include_linebreaks: bool) -> bool {
    for ws in tokens {
        if parseutil::contains_js_whitespace(ws.text().as_str())
            || (include_linebreaks && parseutil::contains_js_linebreak(ws.text().as_str()))
        {
            return true;
        }
    }
    false
}