|
| 1 | +import { TextEditor } from "vscode"; |
| 2 | +import { SyntaxNode } from "web-tree-sitter"; |
| 3 | +import { SimpleScopeTypeType } from "../typings/targetDescriptor.types"; |
| 4 | +import { NodeMatcherAlternative, SelectionWithContext } from "../typings/Types"; |
| 5 | +import { patternFinder } from "../util/nodeFinders"; |
| 6 | +import { |
| 7 | + ancestorChainNodeMatcher, |
| 8 | + argumentMatcher, |
| 9 | + cascadingMatcher, |
| 10 | + createPatternMatchers, |
| 11 | + leadingMatcher, |
| 12 | + matcher, |
| 13 | + patternMatcher, |
| 14 | + trailingMatcher, |
| 15 | +} from "../util/nodeMatchers"; |
| 16 | +import { |
| 17 | + makeNodePairSelection, |
| 18 | + makeRangeFromPositions, |
| 19 | +} from "../util/nodeSelectors"; |
| 20 | + |
| 21 | +// Generated by the following command: |
| 22 | +// `curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-rust/36ae187ed6dd3803a8a89dbb54f3124c8ee74662/src/node-types.STATEMENT_TYPES | jq '[.[] | select(.type == "_declaration_statement") | .subtypes[].type, "expression_statement"]'` |
| 23 | +const STATEMENT_TYPES = [ |
| 24 | + "associated_type", |
| 25 | + "attribute_item", |
| 26 | + "const_item", |
| 27 | + "empty_statement", |
| 28 | + "enum_item", |
| 29 | + "extern_crate_declaration", |
| 30 | + "foreign_mod_item", |
| 31 | + "impl_item", |
| 32 | + "inner_attribute_item", |
| 33 | + "let_declaration", |
| 34 | + "macro_definition", |
| 35 | + "macro_invocation", |
| 36 | + "function_item", |
| 37 | + "function_signature_item", |
| 38 | + "mod_item", |
| 39 | + "static_item", |
| 40 | + "struct_item", |
| 41 | + "trait_item", |
| 42 | + "type_item", |
| 43 | + "union_item", |
| 44 | + "use_declaration", |
| 45 | + "expression_statement", |
| 46 | +]; |
| 47 | + |
| 48 | +/** |
| 49 | + * Scope types allowed to be parents of a statement |
| 50 | + */ |
| 51 | +const STATEMENT_PARENT_TYPES = ["source_file", "block", "declaration_list"]; |
| 52 | + |
| 53 | +/** |
| 54 | + * Returns "impl_item[type]" node higher in the chain |
| 55 | + * @param node The node which we will start our search from |
| 56 | + * @returns node or null |
| 57 | + */ |
| 58 | +function implItemTypeFinder(node: SyntaxNode) { |
| 59 | + if ( |
| 60 | + node.parent?.type === "impl_item" && |
| 61 | + node.parent?.childForFieldName("type")?.equals(node) |
| 62 | + ) { |
| 63 | + return node; |
| 64 | + } |
| 65 | + return null; |
| 66 | +} |
| 67 | + |
| 68 | +function traitBoundExtractor( |
| 69 | + editor: TextEditor, |
| 70 | + node: SyntaxNode |
| 71 | +): SelectionWithContext { |
| 72 | + return { |
| 73 | + selection: makeNodePairSelection(node.children[1], node.lastNamedChild!), |
| 74 | + context: { |
| 75 | + leadingDelimiterRange: makeRangeFromPositions( |
| 76 | + node.children[0].startPosition, |
| 77 | + node.children[1].startPosition |
| 78 | + ), |
| 79 | + }, |
| 80 | + }; |
| 81 | +} |
| 82 | + |
| 83 | +/** |
| 84 | + * Returns the return value node for a given block if we are in a block that has |
| 85 | + * a return value. If the return value expression uses the return keyword then |
| 86 | + * we return the value itself otherwise we just return the expression |
| 87 | + * @param node The node which we might match |
| 88 | + * @returns The return value node |
| 89 | + */ |
| 90 | +function returnValueFinder(node: SyntaxNode) { |
| 91 | + if (node.type !== "block") { |
| 92 | + return null; |
| 93 | + } |
| 94 | + |
| 95 | + const { lastNamedChild } = node; |
| 96 | + |
| 97 | + // The return expression will always be the last statement or expression in |
| 98 | + // the block |
| 99 | + if (lastNamedChild == null) { |
| 100 | + return null; |
| 101 | + } |
| 102 | + |
| 103 | + // If the final name child is an expression statement not a raw expression |
| 104 | + // then we only treat it as a return value if it is a return expression. |
| 105 | + // Otherwise it is just a normal statement that doesn't return anything |
| 106 | + if (lastNamedChild.type === "expression_statement") { |
| 107 | + const expression = lastNamedChild.child(0)!; |
| 108 | + |
| 109 | + if (expression.type === "return_expression") { |
| 110 | + return expression.child(1); |
| 111 | + } |
| 112 | + |
| 113 | + return null; |
| 114 | + } |
| 115 | + |
| 116 | + // Any other type of statement is not a return statement so we should not |
| 117 | + // match it |
| 118 | + if (STATEMENT_TYPES.includes(lastNamedChild.type)) { |
| 119 | + return null; |
| 120 | + } |
| 121 | + |
| 122 | + // NB: At this point we have now excluded all statement types of the only other |
| 123 | + // possible node is an expression node |
| 124 | + |
| 125 | + // If it is a return expression then we zoom down to the actual value of the |
| 126 | + // return expression. This happens when they say `return foo` with no |
| 127 | + // trailing semicolon |
| 128 | + if (lastNamedChild.type === "return_expression") { |
| 129 | + return lastNamedChild.child(1); |
| 130 | + } |
| 131 | + |
| 132 | + // At this point it is an expression which is not a return expression so we |
| 133 | + // just return it as the return value of the block |
| 134 | + return lastNamedChild; |
| 135 | +} |
| 136 | + |
| 137 | +const nodeMatchers: Partial< |
| 138 | + Record<SimpleScopeTypeType, NodeMatcherAlternative> |
| 139 | +> = { |
| 140 | + statement: ancestorChainNodeMatcher( |
| 141 | + [ |
| 142 | + patternFinder(...STATEMENT_PARENT_TYPES), |
| 143 | + patternFinder(...STATEMENT_TYPES), |
| 144 | + ], |
| 145 | + 1 |
| 146 | + ), |
| 147 | + string: ["raw_string_literal", "string_literal"], |
| 148 | + ifStatement: ["if_expression", "if_let_expression"], |
| 149 | + functionCall: ["call_expression", "macro_invocation", "struct_expression"], |
| 150 | + functionCallee: "call_expression[function]", |
| 151 | + comment: ["line_comment", "block_comment"], |
| 152 | + list: ["array_expression", "tuple_expression"], |
| 153 | + collectionItem: argumentMatcher("array_expression", "tuple_expression"), |
| 154 | + namedFunction: "function_item", |
| 155 | + type: cascadingMatcher( |
| 156 | + leadingMatcher( |
| 157 | + [ |
| 158 | + "let_declaration[type]", |
| 159 | + "parameter[type]", |
| 160 | + "field_declaration[type]", |
| 161 | + "const_item[type]", |
| 162 | + ], |
| 163 | + [":"] |
| 164 | + ), |
| 165 | + matcher( |
| 166 | + patternFinder( |
| 167 | + "constrained_type_parameter[bounds]", |
| 168 | + "where_predicate[bounds]" |
| 169 | + ), |
| 170 | + traitBoundExtractor |
| 171 | + ), |
| 172 | + leadingMatcher(["function_item[return_type]"], ["->"]), |
| 173 | + matcher(implItemTypeFinder), |
| 174 | + patternMatcher( |
| 175 | + "struct_item", |
| 176 | + "trait_item", |
| 177 | + "impl_item", |
| 178 | + "array_type[element]" |
| 179 | + ) |
| 180 | + ), |
| 181 | + functionName: ["function_item[name]"], |
| 182 | + anonymousFunction: "closure_expression", |
| 183 | + argumentOrParameter: argumentMatcher( |
| 184 | + "arguments", |
| 185 | + "parameters", |
| 186 | + "meta_arguments", |
| 187 | + "type_parameters" |
| 188 | + ), |
| 189 | + name: [ |
| 190 | + "let_declaration.identifier!", |
| 191 | + "parameter.identifier!", |
| 192 | + "function_item[name]", |
| 193 | + "struct_item[name]", |
| 194 | + "enum_item[name]", |
| 195 | + "trait_item[name]", |
| 196 | + "const_item[name]", |
| 197 | + "meta_item.identifier!", |
| 198 | + "let_declaration[pattern]", |
| 199 | + "constrained_type_parameter[left]", |
| 200 | + "where_predicate[left]", |
| 201 | + ], |
| 202 | + class: ["struct_item", "struct_expression", "enum_item"], |
| 203 | + className: ["struct_item[name]", "enum_item[name]", "trait_item[name]"], |
| 204 | + value: cascadingMatcher( |
| 205 | + leadingMatcher(["let_declaration[value]"], ["="]), |
| 206 | + patternMatcher("meta_item[value]"), |
| 207 | + matcher(returnValueFinder) |
| 208 | + ), |
| 209 | + attribute: trailingMatcher(["mutable_specifier", "attribute_item"]), |
| 210 | +}; |
| 211 | + |
| 212 | +export default createPatternMatchers(nodeMatchers); |
0 commit comments