@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.
Files changed (307) hide show
  1. package/README.md +86 -0
  2. package/dist/ast/builders/array.d.ts +8 -0
  3. package/dist/ast/builders/array.d.ts.map +1 -0
  4. package/dist/ast/builders/array.js +31 -0
  5. package/dist/ast/builders/condition.d.ts +20 -0
  6. package/dist/ast/builders/condition.d.ts.map +1 -0
  7. package/dist/ast/builders/condition.js +28 -0
  8. package/dist/ast/builders/delim.d.ts +11 -0
  9. package/dist/ast/builders/delim.d.ts.map +1 -0
  10. package/dist/ast/builders/delim.js +20 -0
  11. package/dist/ast/builders/error.d.ts +9 -0
  12. package/dist/ast/builders/error.d.ts.map +1 -0
  13. package/dist/ast/builders/error.js +16 -0
  14. package/dist/ast/builders/expression.d.ts +12 -0
  15. package/dist/ast/builders/expression.d.ts.map +1 -0
  16. package/dist/ast/builders/expression.js +31 -0
  17. package/dist/ast/builders/group.d.ts +20 -0
  18. package/dist/ast/builders/group.d.ts.map +1 -0
  19. package/dist/ast/builders/group.js +47 -0
  20. package/dist/ast/builders/index.d.ts +12 -0
  21. package/dist/ast/builders/index.d.ts.map +1 -0
  22. package/dist/ast/builders/index.js +24 -0
  23. package/dist/ast/builders/isFullPos.d.ts +6 -0
  24. package/dist/ast/builders/isFullPos.d.ts.map +1 -0
  25. package/dist/ast/builders/isFullPos.js +6 -0
  26. package/dist/ast/builders/pos.d.ts +21 -0
  27. package/dist/ast/builders/pos.d.ts.map +1 -0
  28. package/dist/ast/builders/pos.js +28 -0
  29. package/dist/ast/builders/token.d.ts +12 -0
  30. package/dist/ast/builders/token.d.ts.map +1 -0
  31. package/dist/ast/builders/token.js +26 -0
  32. package/dist/ast/builders/type.d.ts +6 -0
  33. package/dist/ast/builders/type.d.ts.map +1 -0
  34. package/dist/ast/builders/type.js +37 -0
  35. package/dist/ast/builders/variable.d.ts +17 -0
  36. package/dist/ast/builders/variable.d.ts.map +1 -0
  37. package/dist/ast/builders/variable.js +62 -0
  38. package/dist/ast/classes/ArrayNode.d.ts +18 -0
  39. package/dist/ast/classes/ArrayNode.d.ts.map +1 -0
  40. package/dist/ast/classes/ArrayNode.js +55 -0
  41. package/dist/ast/classes/Condition.d.ts +13 -0
  42. package/dist/ast/classes/Condition.d.ts.map +1 -0
  43. package/dist/ast/classes/Condition.js +21 -0
  44. package/dist/ast/classes/ConditionNode.d.ts +73 -0
  45. package/dist/ast/classes/ConditionNode.d.ts.map +1 -0
  46. package/dist/ast/classes/ConditionNode.js +101 -0
  47. package/dist/ast/classes/ErrorToken.d.ts +27 -0
  48. package/dist/ast/classes/ErrorToken.d.ts.map +1 -0
  49. package/dist/ast/classes/ErrorToken.js +47 -0
  50. package/dist/ast/classes/Expression.d.ts +13 -0
  51. package/dist/ast/classes/Expression.d.ts.map +1 -0
  52. package/dist/ast/classes/Expression.js +19 -0
  53. package/dist/ast/classes/ExpressionNode.d.ts +21 -0
  54. package/dist/ast/classes/ExpressionNode.d.ts.map +1 -0
  55. package/dist/ast/classes/ExpressionNode.js +57 -0
  56. package/dist/ast/classes/GroupNode.d.ts +64 -0
  57. package/dist/ast/classes/GroupNode.d.ts.map +1 -0
  58. package/dist/ast/classes/GroupNode.js +69 -0
  59. package/dist/ast/classes/Node.d.ts +22 -0
  60. package/dist/ast/classes/Node.d.ts.map +1 -0
  61. package/dist/ast/classes/Node.js +28 -0
  62. package/dist/ast/classes/Token.d.ts +27 -0
  63. package/dist/ast/classes/Token.d.ts.map +1 -0
  64. package/dist/ast/classes/Token.js +28 -0
  65. package/dist/ast/classes/ValidToken.d.ts +26 -0
  66. package/dist/ast/classes/ValidToken.d.ts.map +1 -0
  67. package/dist/ast/classes/ValidToken.js +49 -0
  68. package/dist/ast/classes/VariableNode.d.ts +33 -0
  69. package/dist/ast/classes/VariableNode.d.ts.map +1 -0
  70. package/dist/ast/classes/VariableNode.js +58 -0
  71. package/dist/ast/classes/index.d.ts +12 -0
  72. package/dist/ast/classes/index.d.ts.map +1 -0
  73. package/dist/ast/classes/index.js +24 -0
  74. package/dist/ast/handlers.d.ts +42 -0
  75. package/dist/ast/handlers.d.ts.map +1 -0
  76. package/dist/ast/handlers.js +150 -0
  77. package/dist/ast/index.d.ts +4 -0
  78. package/dist/ast/index.d.ts.map +1 -0
  79. package/dist/ast/index.js +8 -0
  80. package/dist/examples/advancedValueComparer.d.ts +3 -0
  81. package/dist/examples/advancedValueComparer.d.ts.map +1 -0
  82. package/dist/examples/advancedValueComparer.js +28 -0
  83. package/dist/examples/shortcutContextParser.d.ts +22 -0
  84. package/dist/examples/shortcutContextParser.d.ts.map +1 -0
  85. package/dist/examples/shortcutContextParser.js +126 -0
  86. package/dist/global.d.js +1 -0
  87. package/dist/grammar/ParserBase.d.ts +51 -0
  88. package/dist/grammar/ParserBase.d.ts.map +1 -0
  89. package/dist/grammar/ParserBase.js +516 -0
  90. package/dist/grammar/createTokens.d.ts +56 -0
  91. package/dist/grammar/createTokens.d.ts.map +1 -0
  92. package/dist/grammar/createTokens.js +843 -0
  93. package/dist/grammar/index.d.ts +3 -0
  94. package/dist/grammar/index.d.ts.map +1 -0
  95. package/dist/grammar/index.js +6 -0
  96. package/dist/helpers/errors.d.ts +9 -0
  97. package/dist/helpers/errors.d.ts.map +1 -0
  98. package/dist/helpers/errors.js +41 -0
  99. package/dist/helpers/general/applyBoolean.d.ts +3 -0
  100. package/dist/helpers/general/applyBoolean.d.ts.map +1 -0
  101. package/dist/helpers/general/applyBoolean.js +17 -0
  102. package/dist/helpers/general/applyPrefix.d.ts +4 -0
  103. package/dist/helpers/general/applyPrefix.d.ts.map +1 -0
  104. package/dist/helpers/general/applyPrefix.js +9 -0
  105. package/dist/helpers/general/defaultConditionNormalizer.d.ts +3 -0
  106. package/dist/helpers/general/defaultConditionNormalizer.d.ts.map +1 -0
  107. package/dist/helpers/general/defaultConditionNormalizer.js +6 -0
  108. package/dist/helpers/general/defaultKeyParser.d.ts +3 -0
  109. package/dist/helpers/general/defaultKeyParser.d.ts.map +1 -0
  110. package/dist/helpers/general/defaultKeyParser.js +8 -0
  111. package/dist/helpers/general/defaultPrefixApplier.d.ts +3 -0
  112. package/dist/helpers/general/defaultPrefixApplier.d.ts.map +1 -0
  113. package/dist/helpers/general/defaultPrefixApplier.js +6 -0
  114. package/dist/helpers/general/defaultValueComparer.d.ts +3 -0
  115. package/dist/helpers/general/defaultValueComparer.d.ts.map +1 -0
  116. package/dist/helpers/general/defaultValueComparer.js +6 -0
  117. package/dist/helpers/general/index.d.ts +7 -0
  118. package/dist/helpers/general/index.d.ts.map +1 -0
  119. package/dist/helpers/general/index.js +14 -0
  120. package/dist/helpers/index.d.ts +4 -0
  121. package/dist/helpers/index.d.ts.map +1 -0
  122. package/dist/helpers/index.js +8 -0
  123. package/dist/helpers/parser/assignParents.d.ts +4 -0
  124. package/dist/helpers/parser/assignParents.d.ts.map +1 -0
  125. package/dist/helpers/parser/assignParents.js +71 -0
  126. package/dist/helpers/parser/checkParserOpts.d.ts +3 -0
  127. package/dist/helpers/parser/checkParserOpts.d.ts.map +1 -0
  128. package/dist/helpers/parser/checkParserOpts.js +126 -0
  129. package/dist/helpers/parser/extractPosition.d.ts +9 -0
  130. package/dist/helpers/parser/extractPosition.d.ts.map +1 -0
  131. package/dist/helpers/parser/extractPosition.js +9 -0
  132. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts +5 -0
  133. package/dist/helpers/parser/getUnclosedRightParenCount.d.ts.map +1 -0
  134. package/dist/helpers/parser/getUnclosedRightParenCount.js +20 -0
  135. package/dist/helpers/parser/index.d.ts +9 -0
  136. package/dist/helpers/parser/index.d.ts.map +1 -0
  137. package/dist/helpers/parser/index.js +18 -0
  138. package/dist/helpers/parser/parseParserOptions.d.ts +4 -0
  139. package/dist/helpers/parser/parseParserOptions.d.ts.map +1 -0
  140. package/dist/helpers/parser/parseParserOptions.js +45 -0
  141. package/dist/helpers/parser/seal.d.ts +8 -0
  142. package/dist/helpers/parser/seal.d.ts.map +1 -0
  143. package/dist/helpers/parser/seal.js +10 -0
  144. package/dist/helpers/parser/setParent.d.ts +6 -0
  145. package/dist/helpers/parser/setParent.d.ts.map +1 -0
  146. package/dist/helpers/parser/setParent.js +4 -0
  147. package/dist/helpers/parser/unescape.d.ts +3 -0
  148. package/dist/helpers/parser/unescape.d.ts.map +1 -0
  149. package/dist/helpers/parser/unescape.js +6 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +17 -0
  153. package/dist/methods/autocomplete.d.ts +18 -0
  154. package/dist/methods/autocomplete.d.ts.map +1 -0
  155. package/dist/methods/autocomplete.js +109 -0
  156. package/dist/methods/autoreplace.d.ts +13 -0
  157. package/dist/methods/autoreplace.d.ts.map +1 -0
  158. package/dist/methods/autoreplace.js +36 -0
  159. package/dist/methods/autosuggest.d.ts +28 -0
  160. package/dist/methods/autosuggest.d.ts.map +1 -0
  161. package/dist/methods/autosuggest.js +371 -0
  162. package/dist/methods/evaluate.d.ts +11 -0
  163. package/dist/methods/evaluate.d.ts.map +1 -0
  164. package/dist/methods/evaluate.js +30 -0
  165. package/dist/methods/getBestIndex.d.ts +19 -0
  166. package/dist/methods/getBestIndex.d.ts.map +1 -0
  167. package/dist/methods/getBestIndex.js +53 -0
  168. package/dist/methods/getIndexes.d.ts +17 -0
  169. package/dist/methods/getIndexes.d.ts.map +1 -0
  170. package/dist/methods/getIndexes.js +97 -0
  171. package/dist/methods/index.d.ts +9 -0
  172. package/dist/methods/index.d.ts.map +1 -0
  173. package/dist/methods/index.js +18 -0
  174. package/dist/methods/normalize.d.ts +12 -0
  175. package/dist/methods/normalize.d.ts.map +1 -0
  176. package/dist/methods/normalize.js +99 -0
  177. package/dist/methods/validate.d.ts +11 -0
  178. package/dist/methods/validate.d.ts.map +1 -0
  179. package/dist/methods/validate.js +111 -0
  180. package/dist/package.js +7 -0
  181. package/dist/package.json.js +193 -0
  182. package/dist/parser.d.ts +58 -0
  183. package/dist/parser.d.ts.map +1 -0
  184. package/dist/parser.js +136 -0
  185. package/dist/types/ast.d.ts +70 -0
  186. package/dist/types/ast.d.ts.map +1 -0
  187. package/dist/types/ast.js +29 -0
  188. package/dist/types/autocomplete.d.ts +143 -0
  189. package/dist/types/autocomplete.d.ts.map +1 -0
  190. package/dist/types/autocomplete.js +24 -0
  191. package/dist/types/errors.d.ts +34 -0
  192. package/dist/types/errors.d.ts.map +1 -0
  193. package/dist/types/errors.js +10 -0
  194. package/dist/types/index.d.ts +5 -0
  195. package/dist/types/index.d.ts.map +1 -0
  196. package/dist/types/index.js +9 -0
  197. package/dist/types/parser.d.ts +451 -0
  198. package/dist/types/parser.d.ts.map +1 -0
  199. package/dist/types/parser.js +1 -0
  200. package/dist/utils/extractTokens.d.ts +8 -0
  201. package/dist/utils/extractTokens.d.ts.map +1 -0
  202. package/dist/utils/extractTokens.js +50 -0
  203. package/dist/utils/getCursorInfo.d.ts +7 -0
  204. package/dist/utils/getCursorInfo.d.ts.map +1 -0
  205. package/dist/utils/getCursorInfo.js +86 -0
  206. package/dist/utils/getOppositeDelimiter.d.ts +6 -0
  207. package/dist/utils/getOppositeDelimiter.d.ts.map +1 -0
  208. package/dist/utils/getOppositeDelimiter.js +35 -0
  209. package/dist/utils/getSurroundingErrors.d.ts +25 -0
  210. package/dist/utils/getSurroundingErrors.d.ts.map +1 -0
  211. package/dist/utils/getSurroundingErrors.js +37 -0
  212. package/dist/utils/index.d.ts +10 -0
  213. package/dist/utils/index.d.ts.map +1 -0
  214. package/dist/utils/index.js +20 -0
  215. package/dist/utils/isBracket.d.ts +3 -0
  216. package/dist/utils/isBracket.d.ts.map +1 -0
  217. package/dist/utils/isBracket.js +7 -0
  218. package/dist/utils/isDelimiter.d.ts +6 -0
  219. package/dist/utils/isDelimiter.d.ts.map +1 -0
  220. package/dist/utils/isDelimiter.js +17 -0
  221. package/dist/utils/isParen.d.ts +3 -0
  222. package/dist/utils/isParen.d.ts.map +1 -0
  223. package/dist/utils/isParen.js +7 -0
  224. package/dist/utils/isQuote.d.ts +4 -0
  225. package/dist/utils/isQuote.d.ts.map +1 -0
  226. package/dist/utils/isQuote.js +7 -0
  227. package/dist/utils/prettyAst.d.ts +35 -0
  228. package/dist/utils/prettyAst.d.ts.map +1 -0
  229. package/dist/utils/prettyAst.js +112 -0
  230. package/package.json +152 -0
  231. package/src/ast/builders/array.ts +45 -0
  232. package/src/ast/builders/condition.ts +56 -0
  233. package/src/ast/builders/delim.ts +39 -0
  234. package/src/ast/builders/error.ts +22 -0
  235. package/src/ast/builders/expression.ts +66 -0
  236. package/src/ast/builders/group.ts +79 -0
  237. package/src/ast/builders/index.ts +13 -0
  238. package/src/ast/builders/isFullPos.ts +10 -0
  239. package/src/ast/builders/pos.ts +57 -0
  240. package/src/ast/builders/token.ts +46 -0
  241. package/src/ast/builders/type.ts +32 -0
  242. package/src/ast/builders/variable.ts +89 -0
  243. package/src/ast/classes/ArrayNode.ts +46 -0
  244. package/src/ast/classes/Condition.ts +22 -0
  245. package/src/ast/classes/ConditionNode.ts +141 -0
  246. package/src/ast/classes/ErrorToken.ts +49 -0
  247. package/src/ast/classes/Expression.ts +26 -0
  248. package/src/ast/classes/ExpressionNode.ts +62 -0
  249. package/src/ast/classes/GroupNode.ts +127 -0
  250. package/src/ast/classes/Node.ts +47 -0
  251. package/src/ast/classes/Token.ts +59 -0
  252. package/src/ast/classes/ValidToken.ts +56 -0
  253. package/src/ast/classes/VariableNode.ts +67 -0
  254. package/src/ast/classes/index.ts +13 -0
  255. package/src/ast/handlers.ts +190 -0
  256. package/src/ast/index.ts +5 -0
  257. package/src/examples/advancedValueComparer.ts +31 -0
  258. package/src/examples/shortcutContextParser.ts +140 -0
  259. package/src/global.d.ts +4 -0
  260. package/src/grammar/ParserBase.ts +715 -0
  261. package/src/grammar/createTokens.ts +512 -0
  262. package/src/grammar/index.ts +4 -0
  263. package/src/helpers/errors.ts +45 -0
  264. package/src/helpers/general/applyBoolean.ts +9 -0
  265. package/src/helpers/general/applyPrefix.ts +7 -0
  266. package/src/helpers/general/defaultConditionNormalizer.ts +9 -0
  267. package/src/helpers/general/defaultKeyParser.ts +8 -0
  268. package/src/helpers/general/defaultPrefixApplier.ts +7 -0
  269. package/src/helpers/general/defaultValueComparer.ts +7 -0
  270. package/src/helpers/general/index.ts +8 -0
  271. package/src/helpers/index.ts +5 -0
  272. package/src/helpers/parser/assignParents.ts +51 -0
  273. package/src/helpers/parser/checkParserOpts.ts +143 -0
  274. package/src/helpers/parser/extractPosition.ts +15 -0
  275. package/src/helpers/parser/getUnclosedRightParenCount.ts +22 -0
  276. package/src/helpers/parser/index.ts +10 -0
  277. package/src/helpers/parser/parseParserOptions.ts +54 -0
  278. package/src/helpers/parser/seal.ts +14 -0
  279. package/src/helpers/parser/setParent.ts +5 -0
  280. package/src/helpers/parser/unescape.ts +4 -0
  281. package/src/index.ts +7 -0
  282. package/src/methods/autocomplete.ts +128 -0
  283. package/src/methods/autoreplace.ts +46 -0
  284. package/src/methods/autosuggest.ts +543 -0
  285. package/src/methods/evaluate.ts +37 -0
  286. package/src/methods/getBestIndex.ts +53 -0
  287. package/src/methods/getIndexes.ts +99 -0
  288. package/src/methods/index.ts +10 -0
  289. package/src/methods/normalize.ts +138 -0
  290. package/src/methods/validate.ts +141 -0
  291. package/src/package.js +11 -0
  292. package/src/parser.ts +183 -0
  293. package/src/types/ast.ts +148 -0
  294. package/src/types/autocomplete.ts +152 -0
  295. package/src/types/errors.ts +40 -0
  296. package/src/types/index.ts +6 -0
  297. package/src/types/parser.ts +479 -0
  298. package/src/utils/extractTokens.ts +67 -0
  299. package/src/utils/getCursorInfo.ts +106 -0
  300. package/src/utils/getOppositeDelimiter.ts +36 -0
  301. package/src/utils/getSurroundingErrors.ts +57 -0
  302. package/src/utils/index.ts +11 -0
  303. package/src/utils/isBracket.ts +6 -0
  304. package/src/utils/isDelimiter.ts +18 -0
  305. package/src/utils/isParen.ts +6 -0
  306. package/src/utils/isQuote.ts +6 -0
  307. 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
+