UNPKG

4.75 kBJavaScriptView Raw
1const semver = require('semver');
2const CONVERSION_OPERATORS = require('./constants/conversion-operators');
3const EXPRESSION_OPERATORS = require('./constants/expression-operators');
4const ACCUMULATORS = require('./constants/accumulators');
5const BSON_TYPES = require('./constants/bson-types');
6const QueryAutoCompleter = require('./query-autocompleter');
7const filter = require('./filter');
8
9/**
10 * String token type.
11 */
12const STRING = 'string';
13
14/**
15 * The proect stage operator.
16 */
17const PROJECT = '$project';
18
19/**
20 * The group stage operator.
21 */
22const GROUP = '$group';
23
24/**
25 * The match operator.
26 */
27const MATCH = '$match';
28
29/**
30 * The dollar const.
31 */
32const DOLLAR = '$';
33
34/**
35 * The base completions.
36 */
37const BASE_COMPLETIONS = EXPRESSION_OPERATORS.concat(
38 CONVERSION_OPERATORS.concat(BSON_TYPES)
39);
40
41/**
42 * Adds autocomplete suggestions based on the aggregation pipeline
43 * operators.
44 */
45class StageAutoCompleter {
46 /**
47 * Get accumulator completions based on the stage identifier.
48 *
49 * @returns {Array} The accumulators.
50 */
51 accumulators() {
52 if (this.stageOperator) {
53 if (this.stageOperator === PROJECT) {
54 return ACCUMULATORS.filter(acc => {
55 return (
56 acc.projectVersion && semver.gte(this.version, acc.projectVersion)
57 );
58 });
59 } else if (this.stageOperator === GROUP) {
60 return ACCUMULATORS;
61 }
62 }
63 return [];
64 }
65
66 /**
67 * Instantiate a new completer.
68 *
69 * @param {String} version - The version.
70 * @param {TextCompleter} textCompleter - The fallback Ace text completer.
71 * @param {Array} fields - The collection fields.
72 * @param {String} stageOperator - The current stage operator.
73 */
74 constructor(version, textCompleter, fields, stageOperator) {
75 this.version = version;
76 this.textCompleter = textCompleter;
77 this.fields = fields;
78 this.variableFields = this.generateVariableFields(fields);
79 this.stageOperator = stageOperator;
80 this.queryAutoCompleter = new QueryAutoCompleter(
81 version,
82 textCompleter,
83 fields
84 );
85 }
86
87 /**
88 * Update the autocompleter with new fields and stage operator.
89 *
90 * @param {Array} fields - The new fields.
91 * @param {String} stageOperator - The stage operator.
92 */
93 update(fields, stageOperator) {
94 this.fields = fields;
95 this.variableFields = this.generateVariableFields(fields);
96 this.queryAutoCompleter.fields = fields;
97 this.stageOperator = stageOperator;
98 }
99
100 /**
101 * Generate variable fields.
102 *
103 * @param {Array} fields - The fields.
104 *
105 * @returns {Array} The variable fields.
106 */
107 generateVariableFields(fields) {
108 return fields.map(field => {
109 return {
110 name: `$${field.name.replace(/"/g, '')}`,
111 value: `$${field.value.replace(/"/g, '')}`,
112 meta: field.meta,
113 version: field.version,
114 score: 1
115 };
116 });
117 }
118
119 /**
120 * Get the completion list for the provided params.
121 *
122 * @param {Editor} editor - The ACE editor.
123 * @param {EditSession} session - The current editor session.
124 * @param {Position} position - The cursor position.
125 * @param {String} prefix - The string prefix to complete.
126 * @param {Function} done - The done callback.
127 *
128 * @returns {Function} The completion function.
129 */
130 getCompletions(editor, session, position, prefix, done) {
131 // Empty prefixes do not return results.
132 if (prefix === '') return done(null, []);
133 // If the current token is a string with single or double quotes, then
134 // we want to use the local text completer instead of suggesting operators.
135 // This is so we can suggest user variable names inside the pipeline that they
136 // have already typed.
137 const currentToken = session.getTokenAt(position.row, position.column);
138 if (currentToken.type === STRING) {
139 if (prefix === DOLLAR) {
140 return done(null, this.variableFields);
141 }
142 return this.textCompleter.getCompletions(
143 editor,
144 session,
145 position,
146 prefix,
147 done
148 );
149 }
150 // Comments block do not return results.
151 if (currentToken.type.includes('comment')) {
152 return done(null, []);
153 }
154 // If the current token is not a string, then we proceed as normal to suggest
155 // operators to the user.
156 if (this.stageOperator && this.stageOperator === MATCH) {
157 this.queryAutoCompleter.getCompletions(
158 editor,
159 session,
160 position,
161 prefix,
162 done
163 );
164 } else {
165 const expressions = BASE_COMPLETIONS.concat(this.accumulators()).concat(
166 this.fields
167 );
168 return done(null, filter(this.version, expressions, prefix));
169 }
170 }
171}
172
173module.exports = StageAutoCompleter;