void> = {
59 | [EventType.Close]: () => this.panel.dispose(),
60 | [EventType.Save]: data => this.save(data),
61 | [EventType.Delete]: () => this.delete()
62 | };
63 |
64 | constructor(
65 | private extensionUri: vscode.Uri,
66 | private contextValue: SchemaType
67 | ) {
68 | const column = vscode.window.activeTextEditor
69 | ? vscode.window.activeTextEditor.viewColumn
70 | : undefined;
71 |
72 | this.panel = vscode.window.createWebviewPanel(
73 | 'createOrEdit',
74 | '',
75 | column || vscode.ViewColumn.One,
76 | {
77 | enableScripts: true,
78 | localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')]
79 | }
80 | );
81 |
82 | this.panel.webview.onDidReceiveMessage(
83 | (data: { type: EventType; data: any }) => {
84 | this.eventHandlers[data.type](data.data);
85 | }
86 | );
87 | }
88 |
89 | async forResource(item: SchemaItem) {
90 | const resource = await vscode.commands.executeCommand(
91 | 'fauna.query',
92 | q.Get(item.ref),
93 | item.parent
94 | );
95 | this.item = item;
96 | this.resource = resource;
97 | return resource;
98 | }
99 |
100 | async setCreateForParent(parent: DBSchemaItem | CollectionSchemaItem) {
101 | this.item = { parent };
102 | }
103 |
104 | async render() {
105 | const styles = this.panel.webview.asWebviewUri(
106 | vscode.Uri.joinPath(this.extensionUri, 'media', 'settingsWebView.css')
107 | );
108 |
109 | const script = this.panel!.webview.asWebviewUri(
110 | vscode.Uri.joinPath(this.extensionUri, 'media', 'settingsWebView.js')
111 | );
112 |
113 | this.panel.title = this.item?.ref
114 | ? `Settings ${this.item.ref.toString()}`
115 | : `Create ${this.contextValue}`;
116 |
117 | const body = await this.renders[this.contextValue].call(this);
118 |
119 | this.panel.webview.html = `
120 |
121 |
122 |
123 |
124 | ${body}
125 |
126 |
127 | `;
128 | }
129 |
130 | private async getRoles() {
131 | const result = await vscode.commands.executeCommand<{
132 | error?: errors.FaunaHTTPError;
133 | data?: values.Ref[];
134 | }>('fauna.query', q.Paginate(q.Roles()), this.item?.parent);
135 |
136 | if (result?.error) {
137 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw);
138 | return;
139 | }
140 |
141 | return result?.data ?? [];
142 | }
143 |
144 | private async getCollections() {
145 | const result = await vscode.commands.executeCommand<{
146 | error?: errors.FaunaHTTPError;
147 | data?: values.Ref[];
148 | }>('fauna.query', q.Paginate(q.Collections()), this.item?.parent);
149 |
150 | if (result?.error) {
151 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw);
152 | return;
153 | }
154 |
155 | return result?.data ?? [];
156 | }
157 |
158 | private async save(data: any) {
159 | this.remapData(data);
160 | let result:
161 | | { error?: errors.FaunaHTTPError; ref?: values.Ref; body?: Expr }
162 | | undefined;
163 | if (this.item?.ref) {
164 | result = await vscode.commands.executeCommand(
165 | 'fauna.query',
166 | q.Update(this.item.ref, data),
167 | this.item.parent
168 | );
169 | } else {
170 | const map: any = {
171 | [SchemaType.Database]: q.CreateDatabase,
172 | [SchemaType.Collection]: q.CreateCollection,
173 | [SchemaType.Index]: q.CreateIndex,
174 | [SchemaType.Function]: q.CreateFunction
175 | };
176 | result = await vscode.commands.executeCommand(
177 | 'fauna.query',
178 | map[this.contextValue](data)
179 | );
180 | }
181 |
182 | this.panel.webview.postMessage({ type: 'release_save' });
183 |
184 | if (result!.error) {
185 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw);
186 | return;
187 | }
188 |
189 | if (!this.resource) {
190 | const item = {
191 | contextValue: this.contextValue,
192 | ref: result!.ref!,
193 | parent: this.item?.parent
194 | };
195 | this.forResource(item).then(() => this.render());
196 |
197 | if (this.contextValue === SchemaType.Function) {
198 | openFQLFile(Expr.toString(result!.body!), item);
199 | }
200 | }
201 |
202 | vscode.window.showInformationMessage(
203 | `${result?.ref} ${this.item?.ref ? 'updated' : 'created'}`
204 | );
205 | vscode.commands.executeCommand('fauna.refreshEntry');
206 | }
207 |
208 | private remapData(data: any) {
209 | if (data.body) {
210 | data.body = evalFQLCode(data.body);
211 | }
212 |
213 | if (data.source) {
214 | data.source = q.Collection(data.source);
215 | }
216 |
217 | if (data.role?.startsWith('@role/')) {
218 | data.role = q.Role(data.role.replace('@role/', ''));
219 | }
220 |
221 | if (data.role === '') {
222 | data.role = null;
223 | }
224 |
225 | if (data.values === '') {
226 | data.values = null;
227 | } else if (data.values) {
228 | data.values = data.values
229 | .replaceAll(' ', '')
230 | .split(',')
231 | .map((value: string) => {
232 | const data = value.split(':');
233 | const reverse = data.length === 2;
234 | const field =
235 | data.length === 1 ? data[0].split('.') : data[1].split('.');
236 | return { field, reverse };
237 | });
238 | }
239 |
240 | if (data.terms === '') {
241 | data.terms = null;
242 | } else if (data.terms) {
243 | data.terms = data.terms
244 | .replaceAll(' ', '')
245 | .split(',')
246 | .map((term: string) => ({ field: term.split('.') }));
247 | }
248 |
249 | return data;
250 | }
251 |
252 | delete() {
253 | if (!this.item) return;
254 | return deleteResource(this.item).then(success => {
255 | if (success) this.panel.dispose();
256 | });
257 | }
258 |
259 | private async renderCollection() {
260 | return `
261 |
288 | `;
289 | }
290 | private async renderDatabase() {
291 | return `
292 |
304 | `;
305 | }
306 |
307 | private async renderIndex() {
308 | const collections = await this.getCollections();
309 | const terms = this.resource
310 | ? this.resource.terms
311 | ? `${this.resource.terms
312 | .map((term: Term) => `${term.field.join('.')} `)
313 | .join('')} `
314 | : 'Not set '
315 | : ' ';
316 |
317 | const values = this.resource
318 | ? this.resource.values
319 | ? `${this.resource.values
320 | .map(
321 | (value: Value) =>
322 | ` ${value.reverse ? 'reverse' : ''} ${value.field.join(
323 | '.'
324 | )} `
325 | )
326 | .join('')} `
327 | : 'Not set (using ref by default) '
328 | : ` `;
329 |
330 | const source = this.resource
331 | ? `${this.resource.source.id} `
332 | : `
333 |
334 | Select a Collection
335 | ${collections!.map(
336 | collection =>
337 | `
338 | ${collection.id}
339 | `
340 | )}
341 | `;
342 |
343 | return `
344 |
395 | `;
396 | }
397 |
398 | private async renderFunction() {
399 | const roles = await this.getRoles();
400 |
401 | return `
402 |
436 | `;
437 | }
438 |
439 | private async renderDocument() {
440 | return '';
441 | }
442 |
443 | private renderTooltip(text: string) {
444 | return `
445 |
449 | `;
450 | }
451 |
452 | private renderControls() {
453 | return `
454 |
455 | CANCEL
456 | ${
457 | this.item?.ref ? 'UPDATE' : 'CREATE'
458 | }
459 |
460 | ${
461 | this.item?.ref
462 | ? `
463 |
464 |
468 | `
469 | : ''
470 | }
471 | `;
472 | }
473 | }
474 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import * as vscode from 'vscode';
4 |
5 | export interface Config {
6 | secret: string;
7 | scheme?: 'http' | 'https';
8 | domain?: string;
9 | port?: number;
10 | graphqlHost?: string;
11 | }
12 |
13 | export function loadConfig(): Config {
14 | const env = loadEnvironmentFile();
15 | const config = vscode.workspace.getConfiguration('fauna');
16 |
17 | const secret = env.FAUNA_KEY || config.get('adminSecretKey', '');
18 |
19 | if (!secret) {
20 | throw new Error('Please provide secret key');
21 | }
22 |
23 | const domain = env.FAUNA_DOMAIN || config.get('domain');
24 | const scheme = env.FAUNA_SCHEME || config.get('scheme');
25 | const port = env.FAUNA_PORT || config.get('port');
26 | // should be explicitly set to a default value as this used to format endpoints and doesn't pass to faunadb-js driver
27 | const graphqlHost =
28 | env.FAUNA_GRAPHQL_HOST ||
29 | config.get('graphqlHost') ||
30 | 'https://graphql.fauna.com';
31 |
32 | return {
33 | secret,
34 | ...(!!scheme && { scheme }),
35 | ...(domain && { domain }),
36 | ...(port && { port }),
37 | ...(graphqlHost && { graphqlHost })
38 | };
39 | }
40 |
41 | interface Env {
42 | FAUNA_KEY?: string;
43 | FAUNA_SCHEME?: 'http' | 'https';
44 | FAUNA_DOMAIN?: string;
45 | FAUNA_PORT?: number;
46 | FAUNA_GRAPHQL_HOST?: string;
47 | }
48 |
49 | function loadEnvironmentFile() {
50 | let env: Env | undefined;
51 |
52 | const { workspaceFolders } = vscode.workspace;
53 | if (workspaceFolders) {
54 | workspaceFolders.find(workspace => {
55 | if (workspace.uri.scheme === 'file') {
56 | const envPath = path.join(workspace.uri.fsPath, '.faunarc');
57 | try {
58 | env = parseEnvironmentFile(fs.readFileSync(envPath, 'utf8'));
59 | return true;
60 | } catch (e) {
61 | if (e.code !== 'ENOENT') {
62 | console.error(`Error parsing ${envPath}\n`, e);
63 | }
64 | }
65 | }
66 | });
67 | }
68 |
69 | return env || {};
70 | }
71 |
72 | function parseEnvironmentFile(src: string) {
73 | const result: { [key: string]: any } = {};
74 | const lines = src.toString().split('\n');
75 | for (const line of lines) {
76 | const match = line.match(/^([^=:#]+?)[=:](.*)/);
77 | if (match) {
78 | const key = match[1].trim();
79 | const value = match[2].trim();
80 | result[key] = value;
81 | }
82 | }
83 | return result;
84 | }
85 |
--------------------------------------------------------------------------------
/src/deleteResource.ts:
--------------------------------------------------------------------------------
1 | import { errors, query as q } from 'faunadb';
2 | import * as vscode from 'vscode';
3 | import { SchemaItem } from './FaunaSchemaProvider';
4 |
5 | export default async (item: Partial>) => {
6 | if (!item.ref) return;
7 | const confirm = await vscode.window.showInformationMessage(
8 | `Would you like to delete ${item.ref}?`,
9 | ...['Yes', 'No']
10 | );
11 | if (confirm === 'Yes') {
12 | const resp = await vscode.commands.executeCommand<{
13 | error: errors.FaunaHTTPError;
14 | }>('fauna.query', q.Delete(item.ref), item.parent);
15 |
16 | if (resp?.error) {
17 | vscode.window.showErrorMessage(resp.error.requestResult.responseRaw);
18 | } else {
19 | vscode.window.showInformationMessage(`${item!.ref} deleted`);
20 | vscode.commands.executeCommand('fauna.refreshEntry');
21 | return true;
22 | }
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { Client, errors, Expr, query as q, values } from 'faunadb';
2 | import * as vscode from 'vscode';
3 | import CollectionSchemaItem from './CollectionSchemaItem';
4 | import { loadConfig } from './config';
5 | import DBSchemaItem from './DBSchemaItem';
6 | import deleteResource from './deleteResource';
7 | import DocumentSchemaItem from './DocumentSchemaItem';
8 | import FaunaSchemaProvider, { SchemaItem } from './FaunaSchemaProvider';
9 | import FQLContentProvider from './FQLContentProvider';
10 | import { openFQLFile, openJSONFile } from './openFQLFile';
11 | import RunAsWebviewProvider from './RunAsWebviewProvider';
12 | import createRunQueryCommand from './runQueryCommand';
13 | import { SettingsWebView } from './SettingsWebView';
14 | import { SchemaType } from './types';
15 | import updateResourceCommand from './updateResourceCommand';
16 | import uploadGraphqlSchemaCommand from './uploadGraphqlSchemaCommand';
17 |
18 | const config = loadConfig();
19 | const client = new Client({
20 | secret: config.secret,
21 | domain: config.domain,
22 | scheme: config.scheme,
23 | port: config.port,
24 | headers: {
25 | 'X-Fauna-Source': 'VSCode'
26 | }
27 | });
28 |
29 | export function activate(context: vscode.ExtensionContext) {
30 | // Set output channel to display FQL results
31 | const outputChannel = vscode.window.createOutputChannel('FQL');
32 |
33 | // Set FQL Document Content Provider
34 | context.subscriptions.push(
35 | vscode.workspace.registerTextDocumentContentProvider(
36 | 'fqlcode',
37 | new FQLContentProvider()
38 | )
39 | );
40 |
41 | // Registered commands and providers that depend on configuration
42 | let registered: vscode.Disposable[] = [];
43 |
44 | register();
45 |
46 | // Reload the extension when reconfigured
47 | const watcher = vscode.workspace.createFileSystemWatcher('./.faunarc');
48 | context.subscriptions.push(
49 | vscode.workspace.onDidChangeConfiguration(
50 | event => event.affectsConfiguration('fauna') && register()
51 | ),
52 | watcher,
53 | watcher.onDidChange(register),
54 | watcher.onDidCreate(register),
55 | watcher.onDidDelete(register)
56 | );
57 |
58 | async function register() {
59 | // Set Schema Provider to display items on sidebar
60 | const faunaSchemaProvider = new FaunaSchemaProvider();
61 | const runAsProvider = new RunAsWebviewProvider(
62 | context.extensionUri,
63 | client,
64 | config.secret
65 | );
66 |
67 | vscode.window.registerWebviewViewProvider('run-as', runAsProvider);
68 | vscode.window.registerTreeDataProvider(
69 | 'fauna-databases',
70 | faunaSchemaProvider
71 | );
72 |
73 | const mountSecret = (scope?: DBSchemaItem | CollectionSchemaItem) => {
74 | const database =
75 | scope instanceof CollectionSchemaItem ? scope.parent : scope;
76 | const secret = config.secret + (database ? ':' + database.path : '');
77 | return config.secret + (database ? ':' + database.path : '') + ':admin';
78 | };
79 |
80 | registered.forEach(reg => reg.dispose());
81 |
82 | registered = [
83 | vscode.commands.registerCommand(
84 | 'fauna.runQuery',
85 | createRunQueryCommand(client, outputChannel, runAsProvider)
86 | ),
87 | vscode.commands.registerCommand(
88 | 'fauna.updateResource',
89 | updateResourceCommand(client)
90 | ),
91 | vscode.commands.registerCommand('fauna.createQuery', () =>
92 | openFQLFile('Paginate(Collections())')
93 | ),
94 | vscode.commands.registerCommand(
95 | 'fauna.uploadGraphQLSchema',
96 | uploadGraphqlSchemaCommand('merge', config, outputChannel)
97 | ),
98 | vscode.commands.registerCommand(
99 | 'fauna.mergeGraphQLSchema',
100 | uploadGraphqlSchemaCommand('merge', config, outputChannel)
101 | ),
102 | vscode.commands.registerCommand(
103 | 'fauna.overrideGraphQLSchema',
104 | uploadGraphqlSchemaCommand('override', config, outputChannel)
105 | ),
106 | vscode.commands.registerCommand(
107 | 'fauna.query',
108 | (expr: Expr, scope?: DBSchemaItem | CollectionSchemaItem) =>
109 | client
110 | .query(expr, {
111 | secret: mountSecret(scope)
112 | })
113 | .catch((error: errors.FaunaHTTPError) => ({ error }))
114 | ),
115 | vscode.commands.registerCommand('fauna.open', (item: SchemaItem) => {
116 | client
117 | .query(item.content!, {
118 | secret: mountSecret(item.parent)
119 | })
120 | .then(async resp => {
121 | if (item.contextValue === SchemaType.Function) {
122 | openFQLFile(Expr.toString(resp.body), item);
123 | return;
124 | }
125 |
126 | if (item.contextValue === SchemaType.Document) {
127 | openJSONFile(JSON.stringify(resp.data, null, '\t'), item);
128 | return;
129 | }
130 |
131 | if (item.contextValue === SchemaType.Index) {
132 | openJSONFile(Expr.toString(q.Object(resp)), item);
133 | return;
134 | }
135 | })
136 | .catch(err => {
137 | console.error(err);
138 | });
139 | }),
140 | vscode.commands.registerCommand('fauna.refreshEntry', () =>
141 | faunaSchemaProvider.refresh()
142 | ),
143 |
144 | vscode.commands.registerCommand('fauna.create', async () => {
145 | const pick = await vscode.window.showQuickPick(
146 | Object.values(SchemaType)
147 | );
148 | if (!pick) return;
149 |
150 | if (pick === SchemaType.Document) {
151 | const response = await vscode.commands.executeCommand<{
152 | collections: values.Ref[];
153 | newId: string;
154 | }>(
155 | 'fauna.query',
156 | q.Let(
157 | {},
158 | {
159 | newId: q.NewId(),
160 | collections: q.Select(['data'], q.Paginate(q.Collections()))
161 | }
162 | )
163 | );
164 |
165 | if (!response?.collections?.length) {
166 | vscode.window.showErrorMessage(
167 | 'You need to create at least one collection'
168 | );
169 | return;
170 | }
171 |
172 | const collection = await vscode.window.showQuickPick(
173 | response.collections.map(c => c.id)
174 | );
175 | if (!collection) return;
176 |
177 | const docItem = new DocumentSchemaItem(
178 | new values.Ref('newDoc'),
179 | new CollectionSchemaItem(new values.Ref(collection))
180 | );
181 |
182 | openJSONFile('{}', docItem);
183 | return;
184 | }
185 |
186 | const view = new SettingsWebView(
187 | context.extensionUri,
188 | pick as SchemaType
189 | );
190 | view.render();
191 | }),
192 |
193 | vscode.commands.registerCommand('fauna.settings', faunaRes => {
194 | const view = new SettingsWebView(
195 | context.extensionUri,
196 | faunaRes.contextValue
197 | );
198 | view.forResource(faunaRes).then(() => view.render());
199 | }),
200 |
201 | vscode.commands.registerCommand('fauna.delete', deleteResource)
202 | ];
203 | }
204 | }
205 |
206 | // this method is called when your extension is deactivated
207 | export function deactivate() {}
208 |
--------------------------------------------------------------------------------
/src/fql.ts:
--------------------------------------------------------------------------------
1 | import { Client, query } from 'faunadb';
2 | import { renderSpecialType } from './specialTypes';
3 | const prettier = require('prettier/standalone');
4 | const plugins = [require('prettier/parser-meriyah')];
5 |
6 | export class InvalidFQL extends Error {}
7 |
8 | export function evalFQLCode(code: string) {
9 | return baseEvalFQL(code, query);
10 | }
11 |
12 | function baseEvalFQL(fql: string, q: typeof query) {
13 | const {
14 | Ref,
15 | Bytes,
16 | Abort,
17 | At,
18 | Let,
19 | Var,
20 | If,
21 | Do,
22 | Object,
23 | Lambda,
24 | Call,
25 | Query,
26 | Map,
27 | Foreach,
28 | Filter,
29 | Take,
30 | Drop,
31 | Prepend,
32 | Append,
33 | IsEmpty,
34 | IsNonEmpty,
35 | Get,
36 | KeyFromSecret,
37 | Paginate,
38 | Exists,
39 | Create,
40 | Update,
41 | Replace,
42 | Delete,
43 | Insert,
44 | Remove,
45 | CreateClass,
46 | CreateCollection,
47 | CreateDatabase,
48 | CreateIndex,
49 | CreateKey,
50 | CreateFunction,
51 | CreateRole,
52 | Singleton,
53 | Events,
54 | Match,
55 | Union,
56 | Intersection,
57 | Difference,
58 | Distinct,
59 | Join,
60 | Login,
61 | Logout,
62 | Identify,
63 | Identity,
64 | HasIdentity,
65 | Concat,
66 | Casefold,
67 | FindStr,
68 | FindStrRegex,
69 | Length,
70 | LowerCase,
71 | LTrim,
72 | NGram,
73 | Repeat,
74 | ReplaceStr,
75 | ReplaceStrRegex,
76 | RTrim,
77 | Space,
78 | SubString,
79 | TitleCase,
80 | Trim,
81 | UpperCase,
82 | Time,
83 | Epoch,
84 | Date,
85 | NextId,
86 | NewId,
87 | Database,
88 | Index,
89 | Class,
90 | Collection,
91 | Function,
92 | Role,
93 | Classes,
94 | Collections,
95 | Databases,
96 | Indexes,
97 | Functions,
98 | Roles,
99 | Keys,
100 | Tokens,
101 | Credentials,
102 | Equals,
103 | Contains,
104 | Select,
105 | SelectAll,
106 | Abs,
107 | Add,
108 | BitAnd,
109 | BitNot,
110 | BitOr,
111 | BitXor,
112 | Ceil,
113 | Divide,
114 | Floor,
115 | Max,
116 | Min,
117 | Modulo,
118 | Multiply,
119 | Round,
120 | Subtract,
121 | Sign,
122 | Sqrt,
123 | Trunc,
124 | Acos,
125 | Asin,
126 | Atan,
127 | Cos,
128 | Cosh,
129 | Degrees,
130 | Exp,
131 | Hypot,
132 | Ln,
133 | Log,
134 | Pow,
135 | Radians,
136 | Sin,
137 | Sinh,
138 | Tan,
139 | Tanh,
140 | LT,
141 | LTE,
142 | GT,
143 | GTE,
144 | And,
145 | Or,
146 | Not,
147 | ToString,
148 | ToNumber,
149 | ToTime,
150 | ToSeconds,
151 | ToMicros,
152 | ToMillis,
153 | DayOfMonth,
154 | DayOfWeek,
155 | DayOfYear,
156 | Second,
157 | Minute,
158 | Hour,
159 | Month,
160 | Year,
161 | ToDate,
162 | Format,
163 | Merge,
164 | Range,
165 | Reduce,
166 | MoveDatabase,
167 | Count,
168 | Mean,
169 | Sum,
170 | StartsWith,
171 | EndsWith,
172 | ContainsStr,
173 | ContainsStrRegex,
174 | RegexEscape,
175 | Now,
176 | ToDouble,
177 | ToInteger,
178 | ToObject,
179 | ToArray,
180 | Any,
181 | All,
182 | TimeAdd,
183 | TimeSubtract,
184 | TimeDiff,
185 | IsNumber,
186 | IsDouble,
187 | IsInteger,
188 | IsBoolean,
189 | IsNull,
190 | IsBytes,
191 | IsTimestamp,
192 | IsDate,
193 | IsString,
194 | IsArray,
195 | IsObject,
196 | IsRef,
197 | IsSet,
198 | IsDoc,
199 | IsLambda,
200 | IsCollection,
201 | IsDatabase,
202 | IsIndex,
203 | IsFunction,
204 | IsKey,
205 | IsToken,
206 | IsCredentials,
207 | IsRole,
208 | Documents,
209 | Reverse,
210 | ContainsPath,
211 | ContainsField,
212 | ContainsValue,
213 | CreateAccessProvider,
214 | AccessProvider,
215 | AccessProviders,
216 | CurrentIdentity,
217 | HasCurrentIdentity,
218 | CurrentToken,
219 | HasCurrentToken
220 | } = q;
221 |
222 | // eslint-disable-next-line
223 | return fql.match(/^\s*{(.*\n*)*}\s*$/) ? eval(`(${fql})`) : eval(fql);
224 | }
225 |
226 | export function parseQueries(code: string): string[] {
227 | const brackets: Record = {
228 | '{': '}',
229 | '(': ')',
230 | '[': ']',
231 | '"': '"',
232 | "'": "'"
233 | };
234 | const openBrackets = new Set(Object.keys(brackets));
235 | const closeBrackets = new Set(Object.values(brackets));
236 | const queries = [];
237 | const stack = [];
238 | let start = 0;
239 | let isOpening;
240 | code = code.trim().split('\n').join('').split('\r').join('');
241 |
242 | for (let i = 0; i < code.length; i++) {
243 | if (openBrackets.has(code[i])) {
244 | stack.push(code[i]);
245 | isOpening = true;
246 | }
247 |
248 | if (closeBrackets.has(code[i]) && brackets[stack.pop()!] !== code[i]) {
249 | throw new InvalidFQL(
250 | `Unexpected closing bracket ${code[i]} at position: ${i + 1}`
251 | );
252 | }
253 |
254 | if (stack.length === 0 && isOpening) {
255 | const line = code.slice(start, i + 1);
256 | if (!line.startsWith('//')) {
257 | queries.push(line);
258 | }
259 | start = i + 1;
260 | isOpening = false;
261 | }
262 | }
263 |
264 | if (isOpening) {
265 | throw new InvalidFQL('Expect all opened brackets to be closed');
266 | }
267 |
268 | return queries;
269 | }
270 |
271 | export function runFQLQuery(code: string, client: Client, secret?: string) {
272 | const queriesArray = parseQueries(code);
273 | if (queriesArray.length === 0) {
274 | throw new InvalidFQL('No queries found');
275 | }
276 |
277 | const wrappedQueries = queriesArray.map(query => {
278 | return client.query(evalFQLCode(query), {
279 | ...(secret && { secret })
280 | });
281 | });
282 |
283 | return Promise.all(wrappedQueries);
284 | }
285 |
286 | export function stringify(obj: object) {
287 | const replacements: string[] = [];
288 |
289 | let string = JSON.stringify(
290 | obj,
291 | (key, value) => {
292 | const parsed = renderSpecialType(value);
293 |
294 | if (parsed) {
295 | const placeHolder = '$$dash_replacement_$' + replacements.length + '$$';
296 | replacements.push(parsed);
297 | return placeHolder;
298 | }
299 |
300 | return value;
301 | },
302 | 2
303 | );
304 |
305 | replacements.forEach((replace, index) => {
306 | string = string.replace('"$$dash_replacement_$' + index + '$$"', replace);
307 | });
308 |
309 | if (string) {
310 | string = string.replace(/\(null\)/g, '()');
311 | }
312 |
313 | return string;
314 | }
315 |
316 | export function formatFQLCode(code: object | string): string {
317 | if (typeof code === 'object') {
318 | code = stringify(code);
319 | }
320 |
321 | try {
322 | return prettier
323 | .format(`(${code})`, { parser: 'meriyah', plugins })
324 | .trim()
325 | .replace(/^(\({)/, '{')
326 | .replace(/(}\);$)/g, '}')
327 | .replace(';', '');
328 | } catch (error) {
329 | return code;
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'faunadb/src/_json' {
2 | export function toJSON(value: object): string;
3 | export function parseJSON(value: string): object;
4 | }
5 |
6 | declare module 'highlight.js/lib/languages/javascript' {
7 | export = Object;
8 | }
9 |
--------------------------------------------------------------------------------
/src/openFQLFile.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as os from 'os';
3 | import * as Path from 'path';
4 | import vscode from 'vscode';
5 | import { SchemaItem } from './FaunaSchemaProvider';
6 | import { formatFQLCode } from './fql';
7 |
8 | const resolvePath = (filepath: string): string => {
9 | if (filepath[0] === '~') {
10 | const hoveVar = process.platform === 'win32' ? 'USERPROFILE' : 'HOME';
11 | return Path.join(
12 | process.env[hoveVar] ?? '',
13 | filepath.slice(1),
14 | 'fauna-vscode-tmp'
15 | );
16 | } else {
17 | return Path.resolve(Path.join(filepath, 'fauna-vscode-tmp'));
18 | }
19 | };
20 |
21 | const tempdir = resolvePath(
22 | vscode.workspace.getConfiguration('createtmpfile').get('tmpDir') ||
23 | os.tmpdir()
24 | );
25 |
26 | if (!fs.existsSync(tempdir)) {
27 | fs.mkdirSync(tempdir);
28 | }
29 |
30 | export async function openJSONFile(content: string, item: SchemaItem) {
31 | try {
32 | const filePath = await saveTmpFile({
33 | item,
34 | content: formatFQLCode(content),
35 | ext: 'js'
36 | });
37 | const doc = await vscode.workspace.openTextDocument(filePath);
38 | vscode.window.showTextDocument(doc);
39 | } catch (err) {
40 | vscode.window.showErrorMessage(err.message);
41 | }
42 | }
43 |
44 | export async function openFQLFile(content: string, item?: SchemaItem) {
45 | try {
46 | let doc;
47 | if (item) {
48 | const filePath = await saveTmpFile({
49 | item,
50 | ext: 'fql',
51 | content: formatFQLCode(content)
52 | });
53 | doc = await vscode.workspace.openTextDocument(filePath);
54 | } else {
55 | doc = await vscode.workspace.openTextDocument({
56 | language: 'fql',
57 | content: 'Paginate(Collections())'
58 | });
59 | }
60 | vscode.window.showTextDocument(doc);
61 | } catch (err) {
62 | vscode.window.showErrorMessage(err.message);
63 | }
64 | }
65 |
66 | function saveTmpFile({
67 | item,
68 | ext,
69 | content
70 | }: {
71 | item: SchemaItem;
72 | ext: string;
73 | content: string;
74 | }): Promise {
75 | const itemName = [[item.contextValue, item.ref.id].join('#'), ext].join('.');
76 | let name;
77 | if (item.parent) {
78 | let parent: SchemaItem | undefined = item.parent;
79 | const paths = [];
80 | while (parent) {
81 | paths.unshift([parent.contextValue, parent.ref.id].join('#'));
82 | parent = parent.parent;
83 | }
84 | name = [...paths, itemName].join('.');
85 | } else {
86 | name = itemName;
87 | }
88 |
89 | const filePath = `${tempdir}${Path.sep}${name}`;
90 | return fs.promises.writeFile(filePath, content).then(() => filePath);
91 | }
92 |
--------------------------------------------------------------------------------
/src/runQueryCommand.ts:
--------------------------------------------------------------------------------
1 | import { Client, errors } from 'faunadb';
2 | import vscode from 'vscode';
3 | import { formatFQLCode, runFQLQuery } from './fql';
4 | import RunAsWebviewProvider from './RunAsWebviewProvider';
5 |
6 | export default (
7 | client: Client,
8 | outputChannel: vscode.OutputChannel,
9 | runAsProvider: RunAsWebviewProvider
10 | ) => async () => {
11 | const { activeTextEditor } = vscode.window;
12 |
13 | if (!activeTextEditor || activeTextEditor.document.languageId !== 'fql') {
14 | vscode.window.showWarningMessage(
15 | 'You have to select a FQL document to run a FQL query.'
16 | );
17 | return;
18 | }
19 |
20 | const selection = activeTextEditor.selection;
21 | const selectedText = activeTextEditor.document.getText(selection);
22 | const fqlExpression =
23 | selectedText.length > 0
24 | ? selectedText
25 | : activeTextEditor.document.getText();
26 | if (fqlExpression.length < 1) {
27 | vscode.window.showWarningMessage(
28 | 'Selected file or selected text must have a FQL query to run'
29 | );
30 |
31 | return;
32 | }
33 |
34 | const runAs = runAsProvider.role ? `(as ${runAsProvider.role})` : '';
35 | outputChannel.appendLine('');
36 | outputChannel.appendLine(`RUNNING ${runAs}: ${fqlExpression}`);
37 | outputChannel.show();
38 |
39 | try {
40 | const result = await runFQLQuery(
41 | fqlExpression,
42 | client,
43 | runAsProvider.getSecretWithRole()
44 | );
45 | const formattedCode = formatFQLCode(result);
46 | outputChannel.appendLine(formattedCode);
47 | } catch (error) {
48 | let message = error.message;
49 |
50 | //@ts-ignore
51 | if (error instanceof errors.FaunaHTTPError) {
52 | message = JSON.stringify(error.errors(), null, 2);
53 | }
54 |
55 | outputChannel.appendLine('ERROR:');
56 | outputChannel.appendLine(message);
57 | }
58 | };
59 |
60 | function truncate(text: string, n: number) {
61 | return text.length > n ? text.substr(0, n - 1) + '...' : text;
62 | }
63 |
--------------------------------------------------------------------------------
/src/specialTypes.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { values as v, Expr } from 'faunadb';
3 |
4 | const ctors = {
5 | classes: 'Class',
6 | collections: 'Collection',
7 | indexes: 'Index',
8 | databases: 'Database',
9 | keys: 'Key',
10 | roles: 'Role'
11 | };
12 |
13 | const parseRef = obj => {
14 | if (obj === undefined) {
15 | return obj;
16 | } else if (obj instanceof v.Ref) {
17 | return obj;
18 | } else {
19 | const ref = '@ref' in obj ? obj['@ref'] : obj;
20 | return new v.Ref(ref.id, parseRef(ref.collection), parseRef(ref.database));
21 | }
22 | };
23 |
24 | const renderRef = obj => {
25 | let args = [`"${obj.id}"`];
26 |
27 | if (obj.collection !== undefined) {
28 | const ctor = ctors[obj.collection.id];
29 | if (ctor !== undefined) {
30 | if (obj.database !== undefined) args.push(renderRef(obj.database));
31 | args = args.join(', ');
32 | return `${ctor}(${args})`;
33 | }
34 | }
35 |
36 | if (obj.collection !== undefined)
37 | args = [renderRef(obj.collection)].concat(args);
38 | args = args.join(', ');
39 | return `Ref(${args})`;
40 | };
41 |
42 | export const renderSpecialType = type => {
43 | if (!type) return null;
44 |
45 | if (type instanceof v.Value) {
46 | if (type instanceof v.Ref) return renderRef(type);
47 | if (type instanceof v.FaunaTime) return `Time("${type.value}")`;
48 | if (type instanceof v.FaunaDate) return `Date("${type.value}")`;
49 | if (type instanceof v.Query) return `Query(${Expr.toString(type.value)})`;
50 | return null;
51 | }
52 |
53 | if (typeof type === 'object' && !Array.isArray(type)) {
54 | const keys = Object.keys(type);
55 |
56 | switch (keys[0]) {
57 | case '@ref':
58 | return renderRef(parseRef(type));
59 | case '@ts':
60 | return renderSpecialType(new v.FaunaTime(type['@ts']));
61 | case '@date':
62 | return renderSpecialType(new v.FaunaDate(type['@date']));
63 | case '@code':
64 | return type['@code'];
65 | case '@query':
66 | return renderSpecialType(new v.Query(type['@query']));
67 | default:
68 | return null;
69 | }
70 | }
71 |
72 | return null;
73 | };
74 |
--------------------------------------------------------------------------------
/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from 'vscode-test';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests');
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/src/test/suite/fql.test.ts:
--------------------------------------------------------------------------------
1 | import { assert } from 'chai';
2 | import { Client } from 'faunadb';
3 | import { afterEach, before } from 'mocha';
4 | import sinon from 'sinon';
5 | import { evalFQLCode, InvalidFQL, runFQLQuery } from '../../fql';
6 |
7 | suite('FQL', () => {
8 | suite('runFQLQuery', () => {
9 | let client: Client;
10 | const queryFake = sinon.fake.resolves({});
11 | before(() => {
12 | client = new Client({ secret: '' });
13 | client.query = queryFake;
14 | });
15 |
16 | afterEach(() => {
17 | queryFake.resetHistory();
18 | });
19 |
20 | test('throw an error for empty query', () => {
21 | assert.throws(
22 | () => runFQLQuery('', client as Client),
23 | InvalidFQL,
24 | 'No queries found'
25 | );
26 | });
27 |
28 | test('throw an error for not closed bracket', () => {
29 | assert.throws(
30 | () => runFQLQuery('Paginate(Indexes()', client),
31 | InvalidFQL,
32 | `Expect all opened brackets to be closed`
33 | );
34 | });
35 |
36 | test('throw an error for invalid closed bracket', () => {
37 | assert.throws(
38 | () => runFQLQuery('Paginate(Indexes(})', client),
39 | InvalidFQL,
40 | 'Unexpected closing bracket } at position: 18'
41 | );
42 | });
43 |
44 | test('run one query in one line', () => {
45 | const fql = 'Paginate(Indexes())';
46 | runFQLQuery(fql, client);
47 | assert.deepEqual(queryFake.firstCall.firstArg, evalFQLCode(fql));
48 | });
49 |
50 | test('run multiple queries in one line', () => {
51 | const fql1 = 'Paginate(Indexes())';
52 | const fql2 = 'Paginate(Collections())';
53 | runFQLQuery([fql1, fql2].join(' '), client);
54 | assert.deepEqual(queryFake.getCall(0).firstArg, evalFQLCode(fql1));
55 | assert.deepEqual(queryFake.getCall(1).firstArg, evalFQLCode(fql2));
56 | });
57 |
58 | test('run one query in multi-lines', () => {
59 | const fql = `
60 | Paginate(
61 | Indexes()
62 | )
63 | `;
64 | runFQLQuery(fql, client);
65 | assert.deepEqual(queryFake.firstCall.firstArg, evalFQLCode(fql));
66 | });
67 |
68 | test('run multiple queries in multi-lines', () => {
69 | const fql = `
70 | Paginate(
71 | Indexes()
72 | )
73 | Paginate(
74 | Collections()
75 | )
76 | `;
77 | runFQLQuery(fql, client);
78 | console.info(queryFake.getCall(0).firstArg);
79 | assert.deepEqual(
80 | queryFake.getCall(0).firstArg,
81 | evalFQLCode('Paginate(Indexes())')
82 | );
83 | assert.deepEqual(
84 | queryFake.getCall(1).firstArg,
85 | evalFQLCode('Paginate(Collections())')
86 | );
87 | });
88 |
89 | test('run multiline queries with comments', () => {
90 | const fql = `
91 | Paginate(Indexes())
92 | // Paginate(Collections())
93 | `;
94 | runFQLQuery(fql, client);
95 | assert.deepEqual(
96 | queryFake.getCall(0).firstArg,
97 | evalFQLCode('Paginate(Indexes())')
98 | );
99 | });
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import Mocha from 'mocha';
3 | import glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd'
9 | });
10 | mocha.useColors(true);
11 |
12 | const testsRoot = path.resolve(__dirname, '..');
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run(failures => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`));
28 | } else {
29 | c();
30 | }
31 | });
32 | } catch (err) {
33 | e(err);
34 | }
35 | });
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export enum SchemaType {
2 | Database = 'database',
3 | Collection = 'collection',
4 | Index = 'index',
5 | Function = 'function',
6 | Document = 'document'
7 | }
8 |
--------------------------------------------------------------------------------
/src/updateResourceCommand.ts:
--------------------------------------------------------------------------------
1 | import { Client, Expr, query as q, values } from 'faunadb';
2 | import * as fs from 'fs';
3 | import * as vscode from 'vscode';
4 | import { loadConfig } from './config';
5 | import { runFQLQuery } from './fql';
6 |
7 | const config = loadConfig();
8 |
9 | const Ref: Record<
10 | string,
11 | {
12 | ref: (name: string, parent?: Expr) => Expr;
13 | payload: (data: string) => string;
14 | }
15 | > = {
16 | function: {
17 | ref: (name: string) => q.Function(name),
18 | payload: (data: string) => `{body: ${data} }`
19 | },
20 | document: {
21 | ref: (name: string, parent?: Expr) => q.Ref(parent!, name),
22 | payload: (data: string) => `{ data: ${data} }`
23 | },
24 | collection: {
25 | ref: (name: string) => q.Collection(name),
26 | payload: () => ''
27 | }
28 | };
29 |
30 | export default (client: Client) => async () => {
31 | const { activeTextEditor } = vscode.window;
32 | if (!activeTextEditor) return;
33 |
34 | const match = activeTextEditor.document.fileName.match(
35 | /fauna-vscode-tmp\/(.*)\.[fql|json]/
36 | );
37 | if (!match) return;
38 |
39 | const parts = match[1].split('.').map(p => p.split('#'));
40 |
41 | let ref: Expr;
42 | let parentRef;
43 | const [itemType, itemId] = parts[parts.length - 1];
44 |
45 | if (parts.length > 1) {
46 | const [parentType, parentId] = parts[parts.length - 2];
47 | parentRef = Ref[parentType].ref(parentId);
48 | ref = Ref[itemType].ref(itemId, parentRef);
49 | } else {
50 | ref = Ref[itemType].ref(itemId);
51 | }
52 |
53 | const data = activeTextEditor.document.getText();
54 |
55 | const dbPaths: string[] = [];
56 | parts.forEach(parts => {
57 | if (parts[0] === 'database') dbPaths.unshift(parts[1]);
58 | });
59 |
60 | const secret = [
61 | dbPaths.length ? `${config.secret}:${dbPaths.join('/')}` : config.secret,
62 | 'admin'
63 | ].join(':');
64 |
65 | const isNewDoc = activeTextEditor.document.fileName.includes('newDoc');
66 |
67 | const query =
68 | isNewDoc && parentRef
69 | ? `Create(${Expr.toString(parentRef)}, ${Ref[itemType].payload(data)})`
70 | : `Update(${Expr.toString(ref)}, ${Ref[itemType].payload(data)})`;
71 |
72 | try {
73 | const resp = await runFQLQuery(query, client, secret);
74 |
75 | const resource = resp[0] as { ref: values.Ref };
76 | vscode.window.showInformationMessage(
77 | `${Expr.toString(resource.ref)} ${isNewDoc ? ' created' : ' updated'}`
78 | );
79 |
80 | if (isNewDoc) {
81 | const newPath = activeTextEditor.document.fileName.replace(
82 | 'newDoc',
83 | resource.ref.id
84 | );
85 | await fs.promises.rename(activeTextEditor.document.fileName, newPath);
86 |
87 | const doc = await vscode.workspace.openTextDocument(newPath);
88 | await vscode.commands.executeCommand(
89 | 'workbench.action.closeActiveEditor'
90 | );
91 | await vscode.commands.executeCommand('fauna.refreshEntry');
92 | vscode.window.showTextDocument(doc, activeTextEditor.viewColumn);
93 | }
94 | } catch (error) {
95 | vscode.window.showErrorMessage(
96 | error.requestResult ? error.requestResult.responseRaw : error.message
97 | );
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/src/uploadGraphqlSchemaCommand.ts:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import vscode from 'vscode';
3 | import { Config } from './config';
4 |
5 | export default (
6 | mode: string = 'merge',
7 | config: Config,
8 | outputChannel: vscode.OutputChannel
9 | ) => async () => {
10 | const { activeTextEditor } = vscode.window;
11 |
12 | const fileName = activeTextEditor?.document.fileName.split('.') || [];
13 |
14 | if (
15 | !activeTextEditor ||
16 | !['graphql', 'gql'].includes(fileName[fileName.length - 1])
17 | ) {
18 | vscode.window.showErrorMessage(
19 | 'Your GraphQL schema file must include the `.graphql` or `.gql` extension.'
20 | );
21 | return;
22 | }
23 |
24 | if (activeTextEditor.document.languageId !== 'graphql') {
25 | vscode.window.showWarningMessage(
26 | 'We recommend to install vscode-graphql extension for syntax highlighting, validation, and language features like go to definition, hover information and autocompletion for graphql projects'
27 | );
28 | }
29 |
30 | const selection = activeTextEditor.selection;
31 | const selectedText = activeTextEditor.document.getText(selection);
32 | const fqlExpression =
33 | selectedText.length > 0
34 | ? selectedText
35 | : activeTextEditor.document.getText();
36 | if (fqlExpression.length < 1) {
37 | vscode.window.showWarningMessage(
38 | 'Selected file or selected text must have a GraphQL Schema to run'
39 | );
40 |
41 | return;
42 | }
43 |
44 | outputChannel.appendLine('');
45 | outputChannel.appendLine(
46 | `UPLOADING SCHEMA (mode=${mode}): ${activeTextEditor.document.fileName}`
47 | );
48 | outputChannel.show();
49 |
50 | try {
51 | const buffer = Buffer.from(fqlExpression, 'utf-8');
52 | const result = await fetch(`${config.graphqlHost}/import?mode=${mode}`, {
53 | method: 'POST',
54 | headers: {
55 | AUTHORIZATION: `Bearer ${config.secret}`
56 | },
57 |
58 | body: buffer
59 | });
60 | outputChannel.appendLine('');
61 | outputChannel.appendLine('RESPONSE:');
62 | outputChannel.appendLine(await result.text());
63 | } catch (error) {
64 | let message = error.message;
65 | outputChannel.appendLine('ERROR:');
66 | outputChannel.appendLine(message);
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/syntaxes/fql.tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | fileTypes
6 |
7 | fql
8 |
9 | name
10 | fql
11 | patterns
12 |
13 |
14 | match
15 | (?
16 | name
17 | constant.numeric.integer.hexadecimal.fql
18 |
19 |
20 | match
21 | (?
22 | name
23 | constant.numeric.float.hexadecimal.fql
24 |
25 |
26 | match
27 | (?
28 | name
29 | constant.numeric.integer.fql
30 |
31 |
32 | match
33 | (?
34 | name
35 | constant.numeric.float.fql
36 |
37 |
38 | begin
39 | '
40 | beginCaptures
41 |
42 | 0
43 |
44 | name
45 | punctuation.definition.string.begin.fql
46 |
47 |
48 | end
49 | '
50 | endCaptures
51 |
52 | 0
53 |
54 | name
55 | punctuation.definition.string.end.fql
56 |
57 |
58 | name
59 | string.quoted.single.fql
60 | patterns
61 |
62 |
63 | include
64 | #escaped_char
65 |
66 |
67 |
68 |
69 | begin
70 | "
71 | beginCaptures
72 |
73 | 0
74 |
75 | name
76 | punctuation.definition.string.begin.fql
77 |
78 |
79 | end
80 | "
81 | endCaptures
82 |
83 | 0
84 |
85 | name
86 | punctuation.definition.string.end.fql
87 |
88 |
89 | name
90 | string.quoted.double.fql
91 | patterns
92 |
93 |
94 | include
95 | #escaped_char
96 |
97 |
98 |
99 |
100 | captures
101 |
102 | 1
103 |
104 | name
105 | punctuation.definition.comment.fql
106 |
107 |
108 | match
109 | \A(#).*$\n?
110 | name
111 | comment.line.shebang.fql
112 |
113 |
114 | begin
115 | (^[ \t]+)?(?=//)
116 | beginCaptures
117 |
118 | 1
119 |
120 | name
121 | punctuation.whitespace.comment.leading.fql
122 |
123 |
124 | end
125 | (?!\G)((?!^)[ \t]+\n)?
126 | endCaptures
127 |
128 | 1
129 |
130 | name
131 | punctuation.whitespace.comment.trailing.fql
132 |
133 |
134 | patterns
135 |
136 |
137 | begin
138 | //
139 | beginCaptures
140 |
141 | 0
142 |
143 | name
144 | punctuation.definition.comment.fql
145 |
146 |
147 | end
148 | \n
149 | name
150 | comment.line.octothorpe.fql
151 |
152 |
153 |
154 |
155 | begin
156 | (^[ \t]+)?(?=/\*)
157 | beginCaptures
158 |
159 | 1
160 |
161 | name
162 | punctuation.whitespace.comment.leading.fql
163 |
164 |
165 | end
166 | (?!\G)((?!^)[ \t]+\*/)?
167 | endCaptures
168 |
169 | 1
170 |
171 | name
172 | punctuation.whitespace.comment.trailing.fql
173 |
174 |
175 | patterns
176 |
177 |
178 | begin
179 | /\*
180 | beginCaptures
181 |
182 | 0
183 |
184 | name
185 | punctuation.definition.comment.fql
186 |
187 |
188 | end
189 | \*/
190 | name
191 | comment.line.octothorpe.fql
192 |
193 |
194 |
195 |
196 | match
197 | \b(If|Else|And|Or|For)\b
198 | name
199 | keyword.control.fql
200 |
201 |
202 | match
203 | (?
204 | name
205 | constant.language.fql
206 |
207 |
208 | match
209 | (?
210 | name
211 | support.function.fql
212 |
213 |
214 | match
215 | (?<=[^.]\.|:)\b([a-zA-Z_][a-zA-Z0-9_]*)
216 | name
217 | variable.other.fql
218 |
219 |
220 | repository
221 |
222 | escaped_char
223 |
224 | patterns
225 |
226 |
227 | match
228 | \\[abfnrtvz\\"'\n]
229 | name
230 | constant.character.escape.fql
231 |
232 |
233 | match
234 | \\\d{1,3}
235 | name
236 | constant.character.escape.byte.fql
237 |
238 |
239 | match
240 | \\x[0-9A-Fa-f][0-9A-Fa-f]
241 | name
242 | constant.character.escape.byte.fql
243 |
244 |
245 | match
246 | \\u\{[0-9A-Fa-f]+\}
247 | name
248 | constant.character.escape.unicode.fql
249 |
250 |
251 | match
252 | \\.
253 | name
254 | invalid.illegal.character.escape.fql
255 |
256 |
257 |
258 |
259 | scopeName
260 | source.fql
261 | uuid
262 | 93E017CC-6F27-11D9-90EB-000D93589AF7
263 |
264 |
265 |
--------------------------------------------------------------------------------
/syntaxes/log.tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | scopeName
6 | code.log
7 | fileTypes
8 |
9 | log
10 |
11 | name
12 | Log file
13 | patterns
14 |
15 |
16 |
17 | match
18 | "(.*?)"
19 | name
20 | string.quoted
21 |
22 |
23 |
24 | match
25 | '(.*?)'
26 | name
27 | string.quoted
28 |
29 |
36 |
37 |
38 | match
39 | \b(?i:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))\b
40 | name
41 | support.class
42 |
43 |
44 |
45 | match
46 | \S+@\S+\.\S+
47 | name
48 | markup.bold
49 |
50 |
51 |
52 | match
53 | \b(?i:((\.)*[a-z]|[0-9])*(Exception|Error|Failure|Fail))\b
54 | name
55 | invalid
56 |
57 |
58 |
59 | match
60 | \b(((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]([0-2]?[0-9]|[3][0-1])[/|\-|\.| ]((19|20)?[0-9]{2})\b
61 | name
62 | constant.numeric
63 |
64 |
65 |
66 | match
67 | \b((19|20)?[0-9]{2}[/|\-|\.| ](((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]([0-2]?[0-9]|[3][0-1]))\b
68 | name
69 | constant.numeric
70 |
71 |
72 |
73 | match
74 | \b([0-2]?[0-9]|[3][0-1])[/|\-|\.| ](((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]((19|20)?[0-9]{2})\b
75 | name
76 | constant.numeric
77 |
78 |
79 |
80 | match
81 | \b([0|1]?[0-9]|2[0-3])\:[0-5][0-9](\:[0-5][0-9])?( ?(?i:(a|p)m?))?( ?[+-]?[0-9]*)?\b
82 | name
83 | constant.numeric
84 |
85 |
86 |
87 | match
88 | \b\d+\.?\d*?\b
89 | name
90 | constant.numeric
91 |
92 |
93 |
94 | match
95 | \b(?i:(0?x)?[0-9a-f][0-9a-f]+)\b
96 | name
97 | constant.numeric
98 |
99 |
100 |
101 | match
102 | \b(?i:(([a-z]|[0-9]|[_|-])*(\.([a-z]|[0-9]|[_|-])*)+))\b
103 | name
104 | support.type
105 |
106 |
107 | match
108 | \b(?i:(Down|Error|Failure|Fail|Fatal|false))(\:|\b)
109 | name
110 | invalid.illegal
111 |
112 |
113 | match
114 | \b(?i:(hint|info|information|true|log))(\:|\b)
115 | name
116 | keyword
117 |
118 |
119 | match
120 | \b(?i:(warning|warn|test|debug|null|undefined|NaN))(\:|\b)
121 | name
122 | invalid.deprecated
123 |
124 |
125 | match
126 | \b(?i:(local))(\:|\b)
127 | name
128 | support.function
129 |
130 |
131 | match
132 | \b(?i:(server|running|remote))(\:|\b)
133 | name
134 | comment.line
135 |
136 |
137 |
138 | match
139 | \b(?i:([a-z]|[0-9])+\:((\/\/)|((\/\/)?(\S)))+)
140 | name
141 | storage
142 |
143 |
144 |
145 | match
146 | (-)+>|├(─)+|└(─)+
147 | name
148 | comment.line
149 |
150 |
151 | uuid
152 | ab259404-3072-4cd4-a943-7cbbd32e373f
153 |
154 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "target": "es6",
6 | "outDir": "out",
7 | "lib": ["es2017", "dom"],
8 | "sourceMap": true,
9 | "rootDir": "src",
10 | "strict": true /* enable all strict type-checking options */
11 | /* Additional Checks */
12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
15 | },
16 | "exclude": ["node_modules", ".vscode-test"]
17 | }
18 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-string-throw": true,
4 | "no-unused-expression": true,
5 | "no-duplicate-variable": true,
6 | "curly": true,
7 | "class-name": true,
8 | "semicolon": [
9 | true,
10 | "always"
11 | ],
12 | "triple-equals": true
13 | },
14 | "defaultSeverity": "warning"
15 | }
16 |
--------------------------------------------------------------------------------