(raw: string)
| 64 | * Always returns a value; never throws. |
| 65 | */ |
| 66 | export function parseQuery(raw: string): ParsedQuery { |
| 67 | const out: ParsedQuery = { |
| 68 | text: '', |
| 69 | kinds: [], |
| 70 | languages: [], |
| 71 | pathFilters: [], |
| 72 | nameFilters: [], |
| 73 | }; |
| 74 | |
| 75 | // Tokenise on whitespace, preserving quoted spans as part of the |
| 76 | // current token. Quotes can appear at the start (`"…"`) OR mid-token |
| 77 | // (`path:"…"`); in both cases everything from the opening `"` to the |
| 78 | // matching `"` is included in the token, whitespace and all. |
| 79 | const tokens: string[] = []; |
| 80 | let i = 0; |
| 81 | while (i < raw.length) { |
| 82 | while (i < raw.length && /\s/.test(raw[i]!)) i++; |
| 83 | if (i >= raw.length) break; |
| 84 | const start = i; |
| 85 | while (i < raw.length && !/\s/.test(raw[i]!)) { |
| 86 | if (raw[i] === '"') { |
| 87 | const end = raw.indexOf('"', i + 1); |
| 88 | if (end === -1) { |
| 89 | // Unterminated quote — swallow the rest of the input as |
| 90 | // one token. Forgiving rather than throwing. |
| 91 | i = raw.length; |
| 92 | break; |
| 93 | } |
| 94 | i = end + 1; |
| 95 | continue; |
| 96 | } |
| 97 | i++; |
| 98 | } |
| 99 | tokens.push(raw.slice(start, i)); |
| 100 | } |
| 101 | |
| 102 | const textParts: string[] = []; |
| 103 | for (const tok of tokens) { |
| 104 | const colon = tok.indexOf(':'); |
| 105 | if (colon <= 0 || colon === tok.length - 1) { |
| 106 | textParts.push(tok); |
| 107 | continue; |
| 108 | } |
| 109 | const key = tok.slice(0, colon).toLowerCase(); |
| 110 | const valueRaw = unquote(tok.slice(colon + 1)); |
| 111 | if (!valueRaw) { |
| 112 | textParts.push(tok); |
| 113 | continue; |
| 114 | } |
| 115 | switch (key) { |
| 116 | case 'kind': { |
| 117 | if (KIND_VALUES.has(valueRaw)) { |
| 118 | out.kinds.push(valueRaw as NodeKind); |
| 119 | } else { |
| 120 | textParts.push(tok); |
| 121 | } |
| 122 | break; |
| 123 | } |
no test coverage detected