@witchcraft/expressit 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/dist/ast/builders/array.d.ts +8 -0
- package/dist/ast/builders/array.d.ts.map +1 -0
- package/dist/ast/builders/array.js +31 -0
- package/dist/ast/builders/condition.d.ts +20 -0
- package/dist/ast/builders/condition.d.ts.map +1 -0
- package/dist/ast/builders/condition.js +28 -0
- package/dist/ast/builders/delim.d.ts +11 -0
- package/dist/ast/builders/delim.d.ts.map +1 -0
- package/dist/ast/builders/delim.js +20 -0
- package/dist/ast/builders/error.d.ts +9 -0
- package/dist/ast/builders/error.d.ts.map +1 -0
- package/dist/ast/builders/error.js +16 -0
- package/dist/ast/builders/expression.d.ts +12 -0
- package/dist/ast/builders/expression.d.ts.map +1 -0
- package/dist/ast/builders/expression.js +31 -0
- package/dist/ast/builders/group.d.ts +20 -0
- package/dist/ast/builders/group.d.ts.map +1 -0
- package/dist/ast/builders/group.js +47 -0
- package/dist/ast/builders/index.d.ts +12 -0
- package/dist/ast/builders/index.d.ts.map +1 -0
- package/dist/ast/builders/index.js +24 -0
- package/dist/ast/builders/isFullPos.d.ts +6 -0
- package/dist/ast/builders/isFullPos.d.ts.map +1 -0
- package/dist/ast/builders/isFullPos.js +6 -0
- package/dist/ast/builders/pos.d.ts +21 -0
- package/dist/ast/builders/pos.d.ts.map +1 -0
- package/dist/ast/builders/pos.js +28 -0
- package/dist/ast/builders/token.d.ts +12 -0
- package/dist/ast/builders/token.d.ts.map +1 -0
- package/dist/ast/builders/token.js +26 -0
- package/dist/ast/builders/type.d.ts +6 -0
- package/dist/ast/builders/type.d.ts.map +1 -0
- package/dist/ast/builders/type.js +37 -0
- package/dist/ast/builders/variable.d.ts +17 -0
- package/dist/ast/builders/variable.d.ts.map +1 -0
- package/dist/ast/builders/variable.js +62 -0
- package/dist/ast/classes/ArrayNode.d.ts +18 -0
- package/dist/ast/classes/ArrayNode.d.ts.map +1 -0
- package/dist/ast/classes/ArrayNode.js +55 -0
- package/dist/ast/classes/Condition.d.ts +13 -0
- package/dist/ast/classes/Condition.d.ts.map +1 -0
- package/dist/ast/classes/Condition.js +21 -0
- package/dist/ast/classes/ConditionNode.d.ts +73 -0
- package/dist/ast/classes/ConditionNode.d.ts.map +1 -0
- package/dist/ast/classes/ConditionNode.js +101 -0
- package/dist/ast/classes/ErrorToken.d.ts +27 -0
- package/dist/ast/classes/ErrorToken.d.ts.map +1 -0
- package/dist/ast/classes/ErrorToken.js +47 -0
- package/dist/ast/classes/Expression.d.ts +13 -0
- package/dist/ast/classes/Expression.d.ts.map +1 -0
- package/dist/ast/classes/Expression.js +19 -0
- package/dist/ast/classes/ExpressionNode.d.ts +21 -0
- package/dist/ast/classes/ExpressionNode.d.ts.map +1 -0
- package/dist/ast/classes/ExpressionNode.js +57 -0
- package/dist/ast/classes/GroupNode.d.ts +64 -0
- package/dist/ast/classes/GroupNode.d.ts.map +1 -0
- package/dist/ast/classes/GroupNode.js +69 -0
- package/dist/ast/classes/Node.d.ts +22 -0
- package/dist/ast/classes/Node.d.ts.map +1 -0
- package/dist/ast/classes/Node.js +28 -0
- package/dist/ast/classes/Token.d.ts +27 -0
- package/dist/ast/classes/Token.d.ts.map +1 -0
- package/dist/ast/classes/Token.js +28 -0
- package/dist/ast/classes/ValidToken.d.ts +26 -0
- package/dist/ast/classes/ValidToken.d.ts.map +1 -0
- package/dist/ast/classes/ValidToken.js +49 -0
- package/dist/ast/classes/VariableNode.d.ts +33 -0
- package/dist/ast/classes/VariableNode.d.ts.map +1 -0
- package/dist/ast/classes/VariableNode.js +58 -0
- package/dist/ast/classes/index.d.ts +12 -0
- package/dist/ast/classes/index.d.ts.map +1 -0
- package/dist/ast/classes/index.js +24 -0
- package/dist/ast/handlers.d.ts +42 -0
- package/dist/ast/handlers.d.ts.map +1 -0
- package/dist/ast/handlers.js +150 -0
- package/dist/ast/index.d.ts +4 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +8 -0
- package/dist/examples/advancedValueComparer.d.ts +3 -0
- package/dist/examples/advancedValueComparer.d.ts.map +1 -0
- package/dist/examples/advancedValueComparer.js +28 -0
- package/dist/examples/shortcutContextParser.d.ts +22 -0
- package/dist/examples/shortcutContextParser.d.ts.map +1 -0
- package/dist/examples/shortcutContextParser.js +126 -0
- package/dist/global.d.js +1 -0
- package/dist/grammar/ParserBase.d.ts +51 -0
- package/dist/grammar/ParserBase.d.ts.map +1 -0
- package/dist/grammar/ParserBase.js +516 -0
- package/dist/grammar/createTokens.d.ts +56 -0
- package/dist/grammar/createTokens.d.ts.map +1 -0
- package/dist/grammar/createTokens.js +843 -0
- package/dist/grammar/index.d.ts +3 -0
- package/dist/grammar/index.d.ts.map +1 -0
- package/dist/grammar/index.js +6 -0
- package/dist/helpers/errors.d.ts +9 -0
- package/dist/helpers/errors.d.ts.map +1 -0
- package/dist/helpers/errors.js +41 -0
- package/dist/helpers/general/applyBoolean.d.ts +3 -0
- package/dist/helpers/general/applyBoolean.d.ts.map +1 -0
- package/dist/helpers/general/applyBoolean.js +17 -0
- package/dist/helpers/general/applyPrefix.d.ts +4 -0
- package/dist/helpers/general/applyPrefix.d.ts.map +1 -0
- package/dist/helpers/general/applyPrefix.js +9 -0
- package/dist/helpers/general/defaultConditionNormalizer.d.ts +3 -0
- package/dist/helpers/general/defaultConditionNormalizer.d.ts.map +1 -0
- package/dist/helpers/general/defaultConditionNormalizer.js +6 -0
- package/dist/helpers/general/defaultKeyParser.d.ts +3 -0
- package/dist/helpers/general/defaultKeyParser.d.ts.map +1 -0
- package/dist/helpers/general/defaultKeyParser.js +8 -0
- package/dist/helpers/general/defaultPrefixApplier.d.ts +3 -0
- package/dist/helpers/general/defaultPrefixApplier.d.ts.map +1 -0
- package/dist/helpers/general/defaultPrefixApplier.js +6 -0
- package/dist/helpers/general/defaultValueComparer.d.ts +3 -0
- package/dist/helpers/general/defaultValueComparer.d.ts.map +1 -0
- package/dist/helpers/general/defaultValueComparer.js +6 -0
- package/dist/helpers/general/index.d.ts +7 -0
- package/dist/helpers/general/index.d.ts.map +1 -0
- package/dist/helpers/general/index.js +14 -0
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +8 -0
- package/dist/helpers/parser/assignParents.d.ts +4 -0
- package/dist/helpers/parser/assignParents.d.ts.map +1 -0
- package/dist/helpers/parser/assignParents.js +71 -0
- package/dist/helpers/parser/checkParserOpts.d.ts +3 -0
- package/dist/helpers/parser/checkParserOpts.d.ts.map +1 -0
- package/dist/helpers/parser/checkParserOpts.js +126 -0
- package/dist/helpers/parser/extractPosition.d.ts +9 -0
- package/dist/helpers/parser/extractPosition.d.ts.map +1 -0
- package/dist/helpers/parser/extractPosition.js +9 -0
- package/dist/helpers/parser/getUnclosedRightParenCount.d.ts +5 -0
- package/dist/helpers/parser/getUnclosedRightParenCount.d.ts.map +1 -0
- package/dist/helpers/parser/getUnclosedRightParenCount.js +20 -0
- package/dist/helpers/parser/index.d.ts +9 -0
- package/dist/helpers/parser/index.d.ts.map +1 -0
- package/dist/helpers/parser/index.js +18 -0
- package/dist/helpers/parser/parseParserOptions.d.ts +4 -0
- package/dist/helpers/parser/parseParserOptions.d.ts.map +1 -0
- package/dist/helpers/parser/parseParserOptions.js +45 -0
- package/dist/helpers/parser/seal.d.ts +8 -0
- package/dist/helpers/parser/seal.d.ts.map +1 -0
- package/dist/helpers/parser/seal.js +10 -0
- package/dist/helpers/parser/setParent.d.ts +6 -0
- package/dist/helpers/parser/setParent.d.ts.map +1 -0
- package/dist/helpers/parser/setParent.js +4 -0
- package/dist/helpers/parser/unescape.d.ts +3 -0
- package/dist/helpers/parser/unescape.d.ts.map +1 -0
- package/dist/helpers/parser/unescape.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/methods/autocomplete.d.ts +18 -0
- package/dist/methods/autocomplete.d.ts.map +1 -0
- package/dist/methods/autocomplete.js +109 -0
- package/dist/methods/autoreplace.d.ts +13 -0
- package/dist/methods/autoreplace.d.ts.map +1 -0
- package/dist/methods/autoreplace.js +36 -0
- package/dist/methods/autosuggest.d.ts +28 -0
- package/dist/methods/autosuggest.d.ts.map +1 -0
- package/dist/methods/autosuggest.js +371 -0
- package/dist/methods/evaluate.d.ts +11 -0
- package/dist/methods/evaluate.d.ts.map +1 -0
- package/dist/methods/evaluate.js +30 -0
- package/dist/methods/getBestIndex.d.ts +19 -0
- package/dist/methods/getBestIndex.d.ts.map +1 -0
- package/dist/methods/getBestIndex.js +53 -0
- package/dist/methods/getIndexes.d.ts +17 -0
- package/dist/methods/getIndexes.d.ts.map +1 -0
- package/dist/methods/getIndexes.js +97 -0
- package/dist/methods/index.d.ts +9 -0
- package/dist/methods/index.d.ts.map +1 -0
- package/dist/methods/index.js +18 -0
- package/dist/methods/normalize.d.ts +12 -0
- package/dist/methods/normalize.d.ts.map +1 -0
- package/dist/methods/normalize.js +99 -0
- package/dist/methods/validate.d.ts +11 -0
- package/dist/methods/validate.d.ts.map +1 -0
- package/dist/methods/validate.js +111 -0
- package/dist/package.js +7 -0
- package/dist/package.json.js +193 -0
- package/dist/parser.d.ts +58 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +136 -0
- package/dist/types/ast.d.ts +70 -0
- package/dist/types/ast.d.ts.map +1 -0
- package/dist/types/ast.js +29 -0
- package/dist/types/autocomplete.d.ts +143 -0
- package/dist/types/autocomplete.d.ts.map +1 -0
- package/dist/types/autocomplete.js +24 -0
- package/dist/types/errors.d.ts +34 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/parser.d.ts +451 -0
- package/dist/types/parser.d.ts.map +1 -0
- package/dist/types/parser.js +1 -0
- package/dist/utils/extractTokens.d.ts +8 -0
- package/dist/utils/extractTokens.d.ts.map +1 -0
- package/dist/utils/extractTokens.js +50 -0
- package/dist/utils/getCursorInfo.d.ts +7 -0
- package/dist/utils/getCursorInfo.d.ts.map +1 -0
- package/dist/utils/getCursorInfo.js +86 -0
- package/dist/utils/getOppositeDelimiter.d.ts +6 -0
- package/dist/utils/getOppositeDelimiter.d.ts.map +1 -0
- package/dist/utils/getOppositeDelimiter.js +35 -0
- package/dist/utils/getSurroundingErrors.d.ts +25 -0
- package/dist/utils/getSurroundingErrors.d.ts.map +1 -0
- package/dist/utils/getSurroundingErrors.js +37 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/isBracket.d.ts +3 -0
- package/dist/utils/isBracket.d.ts.map +1 -0
- package/dist/utils/isBracket.js +7 -0
- package/dist/utils/isDelimiter.d.ts +6 -0
- package/dist/utils/isDelimiter.d.ts.map +1 -0
- package/dist/utils/isDelimiter.js +17 -0
- package/dist/utils/isParen.d.ts +3 -0
- package/dist/utils/isParen.d.ts.map +1 -0
- package/dist/utils/isParen.js +7 -0
- package/dist/utils/isQuote.d.ts +4 -0
- package/dist/utils/isQuote.d.ts.map +1 -0
- package/dist/utils/isQuote.js +7 -0
- package/dist/utils/prettyAst.d.ts +35 -0
- package/dist/utils/prettyAst.d.ts.map +1 -0
- package/dist/utils/prettyAst.js +112 -0
- package/package.json +152 -0
- package/src/ast/builders/array.ts +45 -0
- package/src/ast/builders/condition.ts +56 -0
- package/src/ast/builders/delim.ts +39 -0
- package/src/ast/builders/error.ts +22 -0
- package/src/ast/builders/expression.ts +66 -0
- package/src/ast/builders/group.ts +79 -0
- package/src/ast/builders/index.ts +13 -0
- package/src/ast/builders/isFullPos.ts +10 -0
- package/src/ast/builders/pos.ts +57 -0
- package/src/ast/builders/token.ts +46 -0
- package/src/ast/builders/type.ts +32 -0
- package/src/ast/builders/variable.ts +89 -0
- package/src/ast/classes/ArrayNode.ts +46 -0
- package/src/ast/classes/Condition.ts +22 -0
- package/src/ast/classes/ConditionNode.ts +141 -0
- package/src/ast/classes/ErrorToken.ts +49 -0
- package/src/ast/classes/Expression.ts +26 -0
- package/src/ast/classes/ExpressionNode.ts +62 -0
- package/src/ast/classes/GroupNode.ts +127 -0
- package/src/ast/classes/Node.ts +47 -0
- package/src/ast/classes/Token.ts +59 -0
- package/src/ast/classes/ValidToken.ts +56 -0
- package/src/ast/classes/VariableNode.ts +67 -0
- package/src/ast/classes/index.ts +13 -0
- package/src/ast/handlers.ts +190 -0
- package/src/ast/index.ts +5 -0
- package/src/examples/advancedValueComparer.ts +31 -0
- package/src/examples/shortcutContextParser.ts +140 -0
- package/src/global.d.ts +4 -0
- package/src/grammar/ParserBase.ts +715 -0
- package/src/grammar/createTokens.ts +512 -0
- package/src/grammar/index.ts +4 -0
- package/src/helpers/errors.ts +45 -0
- package/src/helpers/general/applyBoolean.ts +9 -0
- package/src/helpers/general/applyPrefix.ts +7 -0
- package/src/helpers/general/defaultConditionNormalizer.ts +9 -0
- package/src/helpers/general/defaultKeyParser.ts +8 -0
- package/src/helpers/general/defaultPrefixApplier.ts +7 -0
- package/src/helpers/general/defaultValueComparer.ts +7 -0
- package/src/helpers/general/index.ts +8 -0
- package/src/helpers/index.ts +5 -0
- package/src/helpers/parser/assignParents.ts +51 -0
- package/src/helpers/parser/checkParserOpts.ts +143 -0
- package/src/helpers/parser/extractPosition.ts +15 -0
- package/src/helpers/parser/getUnclosedRightParenCount.ts +22 -0
- package/src/helpers/parser/index.ts +10 -0
- package/src/helpers/parser/parseParserOptions.ts +54 -0
- package/src/helpers/parser/seal.ts +14 -0
- package/src/helpers/parser/setParent.ts +5 -0
- package/src/helpers/parser/unescape.ts +4 -0
- package/src/index.ts +7 -0
- package/src/methods/autocomplete.ts +128 -0
- package/src/methods/autoreplace.ts +46 -0
- package/src/methods/autosuggest.ts +543 -0
- package/src/methods/evaluate.ts +37 -0
- package/src/methods/getBestIndex.ts +53 -0
- package/src/methods/getIndexes.ts +99 -0
- package/src/methods/index.ts +10 -0
- package/src/methods/normalize.ts +138 -0
- package/src/methods/validate.ts +141 -0
- package/src/package.js +11 -0
- package/src/parser.ts +183 -0
- package/src/types/ast.ts +148 -0
- package/src/types/autocomplete.ts +152 -0
- package/src/types/errors.ts +40 -0
- package/src/types/index.ts +6 -0
- package/src/types/parser.ts +479 -0
- package/src/utils/extractTokens.ts +67 -0
- package/src/utils/getCursorInfo.ts +106 -0
- package/src/utils/getOppositeDelimiter.ts +36 -0
- package/src/utils/getSurroundingErrors.ts +57 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/isBracket.ts +6 -0
- package/src/utils/isDelimiter.ts +18 -0
- package/src/utils/isParen.ts +6 -0
- package/src/utils/isQuote.ts +6 -0
- package/src/utils/prettyAst.ts +152 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import type { DeepPartial } from "@alanscodelog/utils"
|
|
2
|
+
import { unreachable } from "@alanscodelog/utils"
|
|
3
|
+
|
|
4
|
+
import { pos } from "../ast/builders/pos.js"
|
|
5
|
+
import { ArrayNode } from "../ast/classes/ArrayNode.js"
|
|
6
|
+
import { ConditionNode } from "../ast/classes/ConditionNode.js"
|
|
7
|
+
import { ErrorToken } from "../ast/classes/ErrorToken.js"
|
|
8
|
+
import { GroupNode } from "../ast/classes/GroupNode.js"
|
|
9
|
+
import type { ValidToken } from "../ast/classes/ValidToken.js"
|
|
10
|
+
import { VariableNode } from "../ast/classes/VariableNode.js"
|
|
11
|
+
import type { Parser } from "../parser.js"
|
|
12
|
+
import { type ParserResults, TOKEN_TYPE } from "../types/ast.js"
|
|
13
|
+
import { type Suggestion, SUGGESTION_TYPE } from "../types/autocomplete.js"
|
|
14
|
+
import type { KeywordEntry } from "../types/parser.js"
|
|
15
|
+
import { extractTokens } from "../utils/extractTokens.js"
|
|
16
|
+
import { getCursorInfo } from "../utils/getCursorInfo.js"
|
|
17
|
+
import { getSurroundingErrors } from "../utils/getSurroundingErrors.js"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const defaultNodeDirs = {
|
|
21
|
+
before: false,
|
|
22
|
+
after: false,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const createDefaultRequires = (partial: DeepPartial<Suggestion["requires"]> = {}): Suggestion["requires"] => ({
|
|
26
|
+
whitespace: {
|
|
27
|
+
...defaultNodeDirs,
|
|
28
|
+
...(partial.whitespace ? partial.whitespace : {}),
|
|
29
|
+
},
|
|
30
|
+
group: partial.group ?? false,
|
|
31
|
+
prefix: partial.prefix ?? false,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** Returns if valid token requires whitespace if none between cursor and token. */
|
|
35
|
+
const tokenRequiresWhitespace = (validToken: ValidToken | undefined, whitespace: boolean, wordOps: KeywordEntry[]): boolean => {
|
|
36
|
+
if (whitespace || validToken === undefined) return false
|
|
37
|
+
return validToken.type === TOKEN_TYPE.VALUE ||
|
|
38
|
+
([TOKEN_TYPE.AND, TOKEN_TYPE.OR, TOKEN_TYPE.NOT].includes(validToken.type) &&
|
|
39
|
+
wordOps.find(_ => _.value === validToken.value) !== undefined)
|
|
40
|
+
}
|
|
41
|
+
const tokenVariable = [TOKEN_TYPE.BACKTICK, TOKEN_TYPE.DOUBLEQUOTE, TOKEN_TYPE.SINGLEQUOTE, TOKEN_TYPE.VALUE, TOKEN_TYPE.REGEX]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export class Autosuggest<T extends {}> {
|
|
45
|
+
/**
|
|
46
|
+
* Returns a list of suggestions ( @see Suggestion ). These are not a list of autocomplete entries (with values), but more a list of entries describing possible suggestions. This list can then be passed to @see Parser["autocomplete"] to build a list to show users, from which you can then pick an entry to pass to @see Parser["autoreplace"] .
|
|
47
|
+
*
|
|
48
|
+
* The list returned is "unsorted", but there is still some logic to the order. Fixes for errors are suggested first, in the order returned by @see getSurroundingErrors. Regular suggestions come after in the following order: prefixes if enabled, variables, boolean symbol operators, then boolean word operators.
|
|
49
|
+
*
|
|
50
|
+
* When the cursor is between two tokens that have possible suggestions, only suggestion types for the token before are returned. For example:
|
|
51
|
+
*
|
|
52
|
+
* ```js
|
|
53
|
+
* prop="val"
|
|
54
|
+
* prop|="val" //returns a property suggestions to replace `prop`
|
|
55
|
+
* prop=|"val" //returns a custom operator suggestion to replace `=`
|
|
56
|
+
* prop="|val" //returns a value suggestion
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* And if there are no suggestions for the previous token but there are for the next ones, they are suggested:
|
|
60
|
+
* ```js
|
|
61
|
+
* prop:op:"val"
|
|
62
|
+
* prop:op|:"val" // returns an operator suggestion
|
|
63
|
+
* prop:op:|"val" // returns a value suggestion
|
|
64
|
+
* prop:op|"val" // returns a suggestion for the missing separator
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
autosuggest(input: string, ast: ParserResults, index: number): Suggestion[] {
|
|
68
|
+
// wrapped like this because the function is HUGE
|
|
69
|
+
const opts = (this as any as Parser<T>).options
|
|
70
|
+
const tokens = extractTokens(ast)
|
|
71
|
+
const token = getCursorInfo(input, tokens, index)
|
|
72
|
+
|
|
73
|
+
const wordOps = [...opts.keywords.and, ...opts.keywords.or, ...opts.keywords.not].filter(op => !op.isSymbol)
|
|
74
|
+
|
|
75
|
+
const canSuggestOpAfterPrev = (
|
|
76
|
+
token.valid.prev && tokenVariable.includes(token.valid.prev?.type) &&
|
|
77
|
+
(token.whitespace.prev || token.valid.prev.type === TOKEN_TYPE.PARENR) &&
|
|
78
|
+
!token.at && token.valid.next === undefined
|
|
79
|
+
)
|
|
80
|
+
const canSuggestOpBeforeNext =
|
|
81
|
+
(
|
|
82
|
+
token.valid.next && tokenVariable.includes(token.valid.next?.type) &&
|
|
83
|
+
token.whitespace.next && // no parenL allowed since check since there will already be prefix suggestions
|
|
84
|
+
!token.at && token.valid.prev === undefined
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const requiresWhitespacePrev = tokenRequiresWhitespace(token.valid.prev, token.whitespace.prev, wordOps)
|
|
88
|
+
const requiresWhitespaceNext = tokenRequiresWhitespace(token.valid.next, token.whitespace.next, wordOps)
|
|
89
|
+
|
|
90
|
+
const requiresWhitespacePrevOp = canSuggestOpAfterPrev
|
|
91
|
+
? false
|
|
92
|
+
: requiresWhitespacePrev
|
|
93
|
+
const requireWhitespaceNextOp = !canSuggestOpAfterPrev && canSuggestOpBeforeNext
|
|
94
|
+
? false
|
|
95
|
+
: requiresWhitespaceNext
|
|
96
|
+
|
|
97
|
+
const suggestions: Suggestion[] = []
|
|
98
|
+
if (ast instanceof ErrorToken) {
|
|
99
|
+
suggestions.push({
|
|
100
|
+
type: SUGGESTION_TYPE.PREFIX,
|
|
101
|
+
requires: createDefaultRequires({ group: true }),
|
|
102
|
+
range: pos({ start: index }, { fill: true }),
|
|
103
|
+
isError: true,
|
|
104
|
+
cursorInfo: token,
|
|
105
|
+
})
|
|
106
|
+
suggestions.push({
|
|
107
|
+
type: SUGGESTION_TYPE.VARIABLE,
|
|
108
|
+
requires: createDefaultRequires(),
|
|
109
|
+
range: pos({ start: index }, { fill: true }),
|
|
110
|
+
isError: true,
|
|
111
|
+
cursorInfo: token,
|
|
112
|
+
})
|
|
113
|
+
} else {
|
|
114
|
+
const surroundingErrors = getSurroundingErrors(tokens, token)
|
|
115
|
+
|
|
116
|
+
const errorTypesHandled: TOKEN_TYPE[] = []
|
|
117
|
+
const errorSuggestion = {
|
|
118
|
+
isError: true,
|
|
119
|
+
cursorInfo: token,
|
|
120
|
+
}
|
|
121
|
+
const baseSuggestion = {
|
|
122
|
+
isError: false,
|
|
123
|
+
cursorInfo: token,
|
|
124
|
+
}
|
|
125
|
+
for (const error of surroundingErrors) {
|
|
126
|
+
for (const type of error.expected) {
|
|
127
|
+
if (errorTypesHandled.includes(type)) continue
|
|
128
|
+
errorTypesHandled.push(type)
|
|
129
|
+
|
|
130
|
+
switch (type) {
|
|
131
|
+
case TOKEN_TYPE.DOUBLEQUOTE:
|
|
132
|
+
case TOKEN_TYPE.SINGLEQUOTE:
|
|
133
|
+
case TOKEN_TYPE.BACKTICK: {
|
|
134
|
+
const isLeft = (error.parent as VariableNode).quote!.left === error
|
|
135
|
+
const isRight = (error.parent as VariableNode).quote!.right === error
|
|
136
|
+
suggestions.push({
|
|
137
|
+
...errorSuggestion,
|
|
138
|
+
type: type as any as SUGGESTION_TYPE,
|
|
139
|
+
requires: createDefaultRequires({
|
|
140
|
+
whitespace: {
|
|
141
|
+
before: isRight ? false : requiresWhitespacePrev,
|
|
142
|
+
after: isLeft ? false : requiresWhitespaceNext,
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
range: pos({ start: index }, { fill: true }),
|
|
146
|
+
})
|
|
147
|
+
} break
|
|
148
|
+
case TOKEN_TYPE.AND:
|
|
149
|
+
case TOKEN_TYPE.OR:
|
|
150
|
+
suggestions.push({
|
|
151
|
+
...errorSuggestion,
|
|
152
|
+
type: SUGGESTION_TYPE.BOOLEAN_SYMBOL_OP,
|
|
153
|
+
requires: createDefaultRequires(),
|
|
154
|
+
range: pos({ start: index }, { fill: true }),
|
|
155
|
+
})
|
|
156
|
+
suggestions.push({
|
|
157
|
+
...errorSuggestion,
|
|
158
|
+
type: SUGGESTION_TYPE.BOOLEAN_WORD_OP,
|
|
159
|
+
requires: createDefaultRequires({
|
|
160
|
+
whitespace: {
|
|
161
|
+
before: requiresWhitespacePrevOp,
|
|
162
|
+
after: requireWhitespaceNextOp,
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
range: pos({ start: index }, { fill: true }),
|
|
166
|
+
})
|
|
167
|
+
if (type === TOKEN_TYPE.AND) errorTypesHandled.push(TOKEN_TYPE.OR)
|
|
168
|
+
if (type === TOKEN_TYPE.OR) errorTypesHandled.push(TOKEN_TYPE.AND)
|
|
169
|
+
|
|
170
|
+
break
|
|
171
|
+
case TOKEN_TYPE.PARENL:
|
|
172
|
+
case TOKEN_TYPE.PARENR:
|
|
173
|
+
suggestions.push({
|
|
174
|
+
...errorSuggestion,
|
|
175
|
+
type: type as any as SUGGESTION_TYPE,
|
|
176
|
+
requires: createDefaultRequires(),
|
|
177
|
+
range: pos({ start: index }, { fill: true }),
|
|
178
|
+
})
|
|
179
|
+
break
|
|
180
|
+
case TOKEN_TYPE.VALUE: {
|
|
181
|
+
const prefixedValue = error.parent instanceof VariableNode ? error.parent?.prefix?.value : false
|
|
182
|
+
const isRegexValue = error.parent instanceof VariableNode && (
|
|
183
|
+
error.parent.quote?.left.type === TOKEN_TYPE.REGEX ||
|
|
184
|
+
error.parent.quote?.right.type === TOKEN_TYPE.REGEX
|
|
185
|
+
)
|
|
186
|
+
if (!isRegexValue) {
|
|
187
|
+
// both are always suggested since missing value tokens only happen for variables
|
|
188
|
+
if (!prefixedValue && opts.prefixableGroups) {
|
|
189
|
+
suggestions.push({
|
|
190
|
+
...errorSuggestion,
|
|
191
|
+
type: SUGGESTION_TYPE.PREFIX,
|
|
192
|
+
requires: createDefaultRequires({
|
|
193
|
+
whitespace: {
|
|
194
|
+
before: requiresWhitespacePrev,
|
|
195
|
+
after: false, /* parens get inserted */
|
|
196
|
+
},
|
|
197
|
+
group: true, // is always needed
|
|
198
|
+
}),
|
|
199
|
+
range: pos({ start: index }, { fill: true }),
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
suggestions.push({
|
|
203
|
+
...errorSuggestion,
|
|
204
|
+
type: SUGGESTION_TYPE.VARIABLE,
|
|
205
|
+
requires: createDefaultRequires({
|
|
206
|
+
whitespace: {
|
|
207
|
+
before: requiresWhitespacePrev,
|
|
208
|
+
after: requiresWhitespaceNext,
|
|
209
|
+
},
|
|
210
|
+
prefix: prefixedValue,
|
|
211
|
+
}),
|
|
212
|
+
range: pos({ start: index }, { fill: true }),
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
case TOKEN_TYPE.BRACKETR: {
|
|
218
|
+
suggestions.push({
|
|
219
|
+
...errorSuggestion,
|
|
220
|
+
type: SUGGESTION_TYPE.BRAKCETR,
|
|
221
|
+
requires: createDefaultRequires(),
|
|
222
|
+
range: pos({ start: index }, { fill: true }),
|
|
223
|
+
})
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
case TOKEN_TYPE.OP_EXPANDED_SEP:
|
|
227
|
+
suggestions.push({
|
|
228
|
+
...errorSuggestion,
|
|
229
|
+
type: SUGGESTION_TYPE.PROPERTY_SEP,
|
|
230
|
+
requires: createDefaultRequires(),
|
|
231
|
+
range: pos({ start: index }, { fill: true }),
|
|
232
|
+
})
|
|
233
|
+
break
|
|
234
|
+
case TOKEN_TYPE.REGEX:
|
|
235
|
+
suggestions.push({
|
|
236
|
+
...errorSuggestion,
|
|
237
|
+
type: SUGGESTION_TYPE.REGEX,
|
|
238
|
+
requires: createDefaultRequires(),
|
|
239
|
+
range: pos({ start: index }, { fill: true }),
|
|
240
|
+
})
|
|
241
|
+
break
|
|
242
|
+
case TOKEN_TYPE.OP_CUSTOM:
|
|
243
|
+
case TOKEN_TYPE.BRACKETL:
|
|
244
|
+
case TOKEN_TYPE.NOT:
|
|
245
|
+
unreachable()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The quotes are checked because of situations like `prefix|"var"`.*/
|
|
251
|
+
const prevVar = token.valid.prev?.parent
|
|
252
|
+
const nextVar = token.valid.next?.parent
|
|
253
|
+
const prevCondition = prevVar?.parent
|
|
254
|
+
const nextCondition = nextVar?.parent
|
|
255
|
+
const atVar = token.at?.parent
|
|
256
|
+
const atCondition = atVar?.parent
|
|
257
|
+
|
|
258
|
+
const isVarPrev =
|
|
259
|
+
!token.whitespace.prev &&
|
|
260
|
+
token.valid.prev?.type !== TOKEN_TYPE.REGEX &&
|
|
261
|
+
prevVar instanceof VariableNode &&
|
|
262
|
+
(
|
|
263
|
+
(
|
|
264
|
+
prevCondition instanceof ConditionNode &&
|
|
265
|
+
prevCondition.value === prevVar &&
|
|
266
|
+
(
|
|
267
|
+
prevVar.quote?.right === token.valid.prev ||
|
|
268
|
+
prevVar.value === token.valid.prev
|
|
269
|
+
)
|
|
270
|
+
) ||
|
|
271
|
+
(
|
|
272
|
+
prevCondition instanceof ArrayNode
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const isVarNext =
|
|
277
|
+
!token.whitespace.next &&
|
|
278
|
+
token.valid.next?.type !== TOKEN_TYPE.REGEX &&
|
|
279
|
+
nextVar instanceof VariableNode &&
|
|
280
|
+
(
|
|
281
|
+
(
|
|
282
|
+
nextCondition instanceof ConditionNode &&
|
|
283
|
+
nextCondition.value === nextVar &&
|
|
284
|
+
(
|
|
285
|
+
nextVar.quote?.left === token.valid.next ||
|
|
286
|
+
nextVar.value === token.valid.next
|
|
287
|
+
)
|
|
288
|
+
) ||
|
|
289
|
+
(
|
|
290
|
+
nextCondition instanceof ArrayNode
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const isVarAt = (
|
|
295
|
+
(
|
|
296
|
+
atVar instanceof VariableNode &&
|
|
297
|
+
atCondition instanceof ConditionNode
|
|
298
|
+
) ||
|
|
299
|
+
(
|
|
300
|
+
prevVar instanceof VariableNode &&
|
|
301
|
+
token.valid.prev === prevVar?.quote?.left) ||
|
|
302
|
+
|
|
303
|
+
(
|
|
304
|
+
nextVar instanceof VariableNode &&
|
|
305
|
+
token.valid.next === nextVar?.quote?.right
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
const isPropertyPrev =
|
|
310
|
+
prevCondition instanceof ConditionNode &&
|
|
311
|
+
prevVar !== undefined &&
|
|
312
|
+
prevVar === prevCondition?.property
|
|
313
|
+
const isPropertyNext =
|
|
314
|
+
nextCondition instanceof ConditionNode &&
|
|
315
|
+
nextVar !== undefined &&
|
|
316
|
+
nextVar === nextCondition?.property
|
|
317
|
+
const isPropertyAt =
|
|
318
|
+
atCondition instanceof ConditionNode &&
|
|
319
|
+
atVar !== undefined &&
|
|
320
|
+
atVar === atCondition?.property
|
|
321
|
+
|
|
322
|
+
const isPropertyOperatorPrev = prevVar instanceof ConditionNode && token.valid.prev === prevVar?.propertyOperator
|
|
323
|
+
const isPropertyOperatorNext = nextVar instanceof ConditionNode && token.valid.next === nextVar?.propertyOperator
|
|
324
|
+
const isPropertyOperatorAt = atVar instanceof ConditionNode && token.at === atVar?.propertyOperator
|
|
325
|
+
|
|
326
|
+
/** Situations like `[|]` and `[|` */
|
|
327
|
+
const noArrayValuesTarget = token.valid.prev?.type === TOKEN_TYPE.BRACKETL &&
|
|
328
|
+
(
|
|
329
|
+
token.valid.next === undefined ||
|
|
330
|
+
token.valid.next?.type === TOKEN_TYPE.BRACKETR
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
/** For the following, prev tokens always have priority, next suggestions are only allowed if there are not other prev suggestions. Then lastly, only one at suggestion can exist at a time so no checks needed for those. */
|
|
334
|
+
const target = isVarPrev
|
|
335
|
+
? token.valid.prev
|
|
336
|
+
: !noArrayValuesTarget && !isPropertyPrev && !isPropertyOperatorPrev && isVarNext
|
|
337
|
+
? token.valid.next
|
|
338
|
+
: isVarAt
|
|
339
|
+
? token.at
|
|
340
|
+
: undefined
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
const propertyTarget = isPropertyPrev
|
|
344
|
+
? token.valid.prev
|
|
345
|
+
: !noArrayValuesTarget && !isVarPrev && !isPropertyOperatorPrev && isPropertyNext
|
|
346
|
+
? token.valid.next
|
|
347
|
+
: isPropertyAt
|
|
348
|
+
? token.at
|
|
349
|
+
: undefined
|
|
350
|
+
|
|
351
|
+
const propOpTarget = isPropertyOperatorPrev
|
|
352
|
+
? token.valid.prev
|
|
353
|
+
: !noArrayValuesTarget && !isVarPrev && !isPropertyPrev && isPropertyOperatorNext
|
|
354
|
+
? token.valid.next
|
|
355
|
+
: isPropertyOperatorAt
|
|
356
|
+
? token.at
|
|
357
|
+
: undefined
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
if (target) {
|
|
361
|
+
const parent = target.parent
|
|
362
|
+
if (parent instanceof VariableNode) {
|
|
363
|
+
const range = pos(parent)
|
|
364
|
+
const condition = parent?.parent as ConditionNode
|
|
365
|
+
const isValue = condition.propertyOperator !== undefined && condition.value === parent
|
|
366
|
+
const maybeGroup = parent?.parent?.parent
|
|
367
|
+
const isPrefix = maybeGroup instanceof GroupNode && maybeGroup.prefix === condition
|
|
368
|
+
|
|
369
|
+
// look at whitespace before/after the entire variable
|
|
370
|
+
const varStart = getCursorInfo(input, ast, parent.start)
|
|
371
|
+
const varEnd = getCursorInfo(input, ast, parent.end)
|
|
372
|
+
const targetRequiresWhitespacePrev = tokenRequiresWhitespace(varStart.valid.prev, varStart.whitespace.prev, wordOps)
|
|
373
|
+
const targetRequiresWhitespaceNext = tokenRequiresWhitespace(varEnd.valid.next, varEnd.whitespace.next, wordOps)
|
|
374
|
+
const prefixedValue = target.parent instanceof VariableNode ? target.parent?.prefix?.value : false
|
|
375
|
+
|
|
376
|
+
// most of these require additional handling below
|
|
377
|
+
const isSepPrev = token.prev?.type === TOKEN_TYPE.OP_EXPANDED_SEP
|
|
378
|
+
const arrayValue = target.parent?.parent instanceof ArrayNode
|
|
379
|
+
const isRegexFlag = target === parent.quote?.flags
|
|
380
|
+
|
|
381
|
+
if (!isRegexFlag && !isSepPrev && !isValue && !arrayValue && !prefixedValue && opts.prefixableGroups) {
|
|
382
|
+
suggestions.push({
|
|
383
|
+
...baseSuggestion,
|
|
384
|
+
type: SUGGESTION_TYPE.PREFIX,
|
|
385
|
+
requires: createDefaultRequires({
|
|
386
|
+
group: !isPrefix,
|
|
387
|
+
whitespace: {
|
|
388
|
+
before: targetRequiresWhitespacePrev && !isPrefix,
|
|
389
|
+
after: false, // parens exist or get inserted
|
|
390
|
+
},
|
|
391
|
+
}),
|
|
392
|
+
range,
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!isRegexFlag && !isPrefix) {
|
|
397
|
+
suggestions.push({
|
|
398
|
+
...baseSuggestion,
|
|
399
|
+
type: arrayValue
|
|
400
|
+
? SUGGESTION_TYPE.ARRAY_VALUE
|
|
401
|
+
: isValue
|
|
402
|
+
? SUGGESTION_TYPE.VALUE
|
|
403
|
+
: SUGGESTION_TYPE.VARIABLE,
|
|
404
|
+
requires: createDefaultRequires({
|
|
405
|
+
whitespace: {
|
|
406
|
+
before: targetRequiresWhitespacePrev,
|
|
407
|
+
after: targetRequiresWhitespaceNext,
|
|
408
|
+
},
|
|
409
|
+
prefix: prefixedValue,
|
|
410
|
+
}),
|
|
411
|
+
range,
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (noArrayValuesTarget) {
|
|
418
|
+
suggestions.push({
|
|
419
|
+
...baseSuggestion,
|
|
420
|
+
type: SUGGESTION_TYPE.ARRAY_VALUE,
|
|
421
|
+
requires: createDefaultRequires(),
|
|
422
|
+
range: pos({ start: index }, { fill: true }),
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (propertyTarget) {
|
|
427
|
+
suggestions.push({
|
|
428
|
+
...baseSuggestion,
|
|
429
|
+
type: SUGGESTION_TYPE.PROPERTY,
|
|
430
|
+
requires: createDefaultRequires(),
|
|
431
|
+
range: pos(propertyTarget),
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
if (propOpTarget) {
|
|
435
|
+
suggestions.push({
|
|
436
|
+
...baseSuggestion,
|
|
437
|
+
type: (propOpTarget.parent as ConditionNode).sep
|
|
438
|
+
? SUGGESTION_TYPE.EXPANDED_PROPERTY_OPERATOR
|
|
439
|
+
: SUGGESTION_TYPE.CUSTOM_PROPERTY_OPERATOR
|
|
440
|
+
,
|
|
441
|
+
requires: createDefaultRequires(),
|
|
442
|
+
range: pos(propOpTarget),
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const canSuggestValue =
|
|
447
|
+
(
|
|
448
|
+
(
|
|
449
|
+
token.whitespace.next &&
|
|
450
|
+
(
|
|
451
|
+
token.whitespace.prev ||
|
|
452
|
+
token.prev?.type === TOKEN_TYPE.BRACKETL ||
|
|
453
|
+
token.prev?.type === TOKEN_TYPE.PARENL
|
|
454
|
+
)
|
|
455
|
+
) ||
|
|
456
|
+
(
|
|
457
|
+
token.whitespace.prev &&
|
|
458
|
+
(
|
|
459
|
+
token.whitespace.next ||
|
|
460
|
+
token.next?.type === TOKEN_TYPE.BRACKETR ||
|
|
461
|
+
token.next?.type === TOKEN_TYPE.PARENR
|
|
462
|
+
)
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
if (canSuggestValue) {
|
|
467
|
+
const inArrayNode = [nextCondition, prevCondition, nextVar, prevVar].find(_ => _ instanceof ArrayNode) !== undefined
|
|
468
|
+
const opsNotNeeded = ["and", "or"].includes(opts.onMissingBooleanOperator)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
if (inArrayNode || opsNotNeeded) {
|
|
472
|
+
suggestions.push({
|
|
473
|
+
type: inArrayNode ? SUGGESTION_TYPE.ARRAY_VALUE : SUGGESTION_TYPE.VARIABLE,
|
|
474
|
+
requires: createDefaultRequires({}),
|
|
475
|
+
range: pos({ start: index }, { fill: true }),
|
|
476
|
+
...baseSuggestion,
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
// if we're not an in array node we can also suggest prefixes
|
|
480
|
+
if (!inArrayNode && opsNotNeeded) {
|
|
481
|
+
suggestions.push({
|
|
482
|
+
...baseSuggestion,
|
|
483
|
+
type: SUGGESTION_TYPE.PREFIX,
|
|
484
|
+
requires: createDefaultRequires({
|
|
485
|
+
group: true,
|
|
486
|
+
}),
|
|
487
|
+
range: pos({ start: index }, { fill: true }),
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const canSuggestRegexFlags =
|
|
493
|
+
// has existing flags before/after
|
|
494
|
+
(
|
|
495
|
+
token.at &&
|
|
496
|
+
token.at === (token.at?.parent as VariableNode)?.quote?.flags
|
|
497
|
+
) ||
|
|
498
|
+
(
|
|
499
|
+
token.valid.prev &&
|
|
500
|
+
token.valid.prev === (token.valid.prev?.parent as VariableNode)?.quote?.flags
|
|
501
|
+
) ||
|
|
502
|
+
(
|
|
503
|
+
token.valid.next &&
|
|
504
|
+
token.valid.next === (token.valid.next?.parent as VariableNode)?.quote?.flags
|
|
505
|
+
) ||
|
|
506
|
+
( // no flags
|
|
507
|
+
token.valid.prev?.type === TOKEN_TYPE.REGEX &&
|
|
508
|
+
token.valid.prev === (token.valid.prev.parent as VariableNode).quote?.right
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if (canSuggestRegexFlags) {
|
|
512
|
+
suggestions.push({
|
|
513
|
+
...baseSuggestion,
|
|
514
|
+
type: SUGGESTION_TYPE.REGEX_FLAGS,
|
|
515
|
+
requires: createDefaultRequires(),
|
|
516
|
+
range: pos({ start: index }, { fill: true }),
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (canSuggestOpAfterPrev || canSuggestOpBeforeNext) {
|
|
521
|
+
const range = pos({ start: index }, { fill: true })
|
|
522
|
+
suggestions.push({
|
|
523
|
+
...baseSuggestion,
|
|
524
|
+
type: SUGGESTION_TYPE.BOOLEAN_SYMBOL_OP,
|
|
525
|
+
requires: createDefaultRequires(),
|
|
526
|
+
range,
|
|
527
|
+
})
|
|
528
|
+
suggestions.push({
|
|
529
|
+
...baseSuggestion,
|
|
530
|
+
type: SUGGESTION_TYPE.BOOLEAN_WORD_OP,
|
|
531
|
+
requires: createDefaultRequires({
|
|
532
|
+
whitespace: {
|
|
533
|
+
before: requiresWhitespacePrevOp,
|
|
534
|
+
after: requireWhitespaceNextOp,
|
|
535
|
+
},
|
|
536
|
+
}),
|
|
537
|
+
range,
|
|
538
|
+
})
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return suggestions
|
|
542
|
+
}
|
|
543
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AddParameters, get, unreachable } from "@alanscodelog/utils"
|
|
2
|
+
|
|
3
|
+
import { Condition } from "../ast/classes/Condition.js"
|
|
4
|
+
import { Expression } from "../ast/classes/Expression.js"
|
|
5
|
+
import type { Parser } from "../parser.js"
|
|
6
|
+
import { TOKEN_TYPE } from "../types/ast.js"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export class EvaluateMixin<T extends {}> {
|
|
10
|
+
/**
|
|
11
|
+
* Evaluates a {@link Parser.normalize normalized} ast.
|
|
12
|
+
*
|
|
13
|
+
* How the ast is evaluated for different operators can be controlled by the {@link ParserOptions.valueComparer valueComparer} option.
|
|
14
|
+
*/
|
|
15
|
+
evaluate(ast: Expression<any, any> | Condition<any, any>, context: Record<string, any>): boolean {
|
|
16
|
+
// @ts-expect-error private method
|
|
17
|
+
this._checkEvaluationOptions()
|
|
18
|
+
const opts = (this as any as Parser<T>).options
|
|
19
|
+
|
|
20
|
+
const self_ = this as any as EvaluateMixin<T> & { evaluate: AddParameters<EvaluateMixin<T>["evaluate"], []> }
|
|
21
|
+
if (ast instanceof Condition) {
|
|
22
|
+
const contextValue = get(context, ast.property)
|
|
23
|
+
const res = opts.valueComparer({ property: ast.property, value: ast.value, operator: ast.operator }, contextValue, context)
|
|
24
|
+
return ast.negate ? !res : res
|
|
25
|
+
}
|
|
26
|
+
if (ast instanceof Expression) {
|
|
27
|
+
const left = self_.evaluate(ast.left, context)
|
|
28
|
+
const right = self_.evaluate(ast.right, context)
|
|
29
|
+
|
|
30
|
+
return ast.operator === TOKEN_TYPE.AND
|
|
31
|
+
? (left && right)
|
|
32
|
+
: (left || right)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return unreachable()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class GetBestIndexesMixin {
|
|
2
|
+
/**
|
|
3
|
+
* Given the set of indexes returned by {@link getBestIndex}, the set of existing indexes in a database, and the index to sort by\*, will return a list of the best/shortest sets of indexes.
|
|
4
|
+
*
|
|
5
|
+
* For example, given the query `a && b && c`, `getBestIndex` will return `[Set(a), Set(b)]`.
|
|
6
|
+
*
|
|
7
|
+
* Suppose we have indexes on all the variables and that the user wants to sort by `c`, this function will return [`Set(c)`].
|
|
8
|
+
*
|
|
9
|
+
* Suppose instead we have indexes only on `a` and `b` and that the user wants to sort by `c`, this function will return [`Set(a), Set(b)`]. Either can be picked by some other criteria (e.g. size of the indexes). Sort should then be done in memory.
|
|
10
|
+
*
|
|
11
|
+
* And then finally, if we have no existing indexes on any of the variables, the function will return `[]`.
|
|
12
|
+
*
|
|
13
|
+
* Note: This is a simple algorithm and is not designed to take into account instances where entries are indexed by two or more properties as their keys (i.e. multicolumn indexes).
|
|
14
|
+
*
|
|
15
|
+
* \* If the sort index is not in the list of existing indexes it is not taken into account.
|
|
16
|
+
*/
|
|
17
|
+
getBestIndexes(indexes: Set<string>[], existing: Set<string> | Map<string, number>, sortIndex: string = ""): Set<string>[] {
|
|
18
|
+
indexes = indexes.filter(set => {
|
|
19
|
+
for (const key of set) {
|
|
20
|
+
if (!existing.has(key)) return false
|
|
21
|
+
}
|
|
22
|
+
return true
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
let finalIndexes = indexes
|
|
26
|
+
|
|
27
|
+
if (existing.has(sortIndex)) {
|
|
28
|
+
const indexesWithSortIndex = indexes.filter(set => set.has(sortIndex))
|
|
29
|
+
if (indexesWithSortIndex.length > 0) finalIndexes = indexesWithSortIndex
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
let smallest = Infinity
|
|
34
|
+
if (existing instanceof Map) {
|
|
35
|
+
const scores = new Map<Set<string>, number>()
|
|
36
|
+
for (const set of finalIndexes) {
|
|
37
|
+
let score = 0
|
|
38
|
+
for (const key of set) {
|
|
39
|
+
score += existing.get(key) ?? 0
|
|
40
|
+
}
|
|
41
|
+
scores.set(set, score)
|
|
42
|
+
smallest = score < smallest ? score : smallest
|
|
43
|
+
}
|
|
44
|
+
return indexes.filter(set => smallest === Infinity || scores.get(set) === smallest)
|
|
45
|
+
} else {
|
|
46
|
+
for (const set of finalIndexes) {
|
|
47
|
+
smallest = set.size < smallest ? set.size : smallest
|
|
48
|
+
}
|
|
49
|
+
return indexes.filter(set => smallest === Infinity || set.size === smallest)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|