├── src ├── shims.d.ts ├── test │ ├── suite │ │ ├── Utils │ │ │ ├── example.test.rules │ │ │ └── Utils.test.ts │ │ ├── syntax-highlighting │ │ │ ├── rules │ │ │ │ ├── allow-comment.rules │ │ │ │ ├── root-comment.rules │ │ │ │ ├── allow-without-condition-comment.rules │ │ │ │ ├── service-comment.rules │ │ │ │ └── matcher-comment.rules │ │ │ └── comments.test.ts │ │ ├── autoformatter │ │ │ ├── functionNamedMatches.formatted.rules │ │ │ ├── functionNamedMatches.test.rules │ │ │ ├── matcherNoSemicolon.formatted.rules │ │ │ ├── matcherNoSemicolon.test.rules │ │ │ ├── complex.formatted.rules │ │ │ ├── autoformatter.test.ts │ │ │ └── complex.test.rules │ │ ├── extension.test.ts │ │ ├── language.test.ts │ │ └── index.ts │ ├── runTest.ts │ ├── runTestLegacy.ts │ └── example.rules ├── providers │ ├── index.ts │ ├── FirestoreHoverProvider.ts │ ├── FirestoreCompletionProvider.ts │ └── FirestoreFormattingProvider.ts ├── extension.ts ├── documentation │ ├── types.ts │ ├── keywordDocumentation.ts │ ├── methodDocumentation.ts │ └── typeDocumentation.ts ├── utils │ ├── index.ts │ └── textmate │ │ ├── scope-info.ts │ │ ├── textmate.ts │ │ └── text-util.ts └── Documentation.ts ├── .gitignore ├── resources ├── firecode.png ├── autoformat.gif ├── autocomplete.gif ├── mouseover-info.gif └── syntax-highlighting.png ├── .eslintignore ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .eslintrc.js ├── dev-troubleshooting.md ├── tsconfig.json ├── .travis.yml ├── language-configuration.json ├── LICENSE ├── .github └── workflows │ └── codeql-analysis.yml ├── README.md ├── CHANGELOG.md ├── package.json ├── syntaxes └── firestorerules.tmLanguage.json └── yarn.lock /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'prettier-plugin-firestore-rules'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /resources/firecode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChFlick/firecode/HEAD/resources/firecode.png -------------------------------------------------------------------------------- /resources/autoformat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChFlick/firecode/HEAD/resources/autoformat.gif -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | coverage 4 | jest.config.js 5 | src/parser/parser.js 6 | .eslintrc.js -------------------------------------------------------------------------------- /resources/autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChFlick/firecode/HEAD/resources/autocomplete.gif -------------------------------------------------------------------------------- /resources/mouseover-info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChFlick/firecode/HEAD/resources/mouseover-info.gif -------------------------------------------------------------------------------- /resources/syntax-highlighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChFlick/firecode/HEAD/resources/syntax-highlighting.png -------------------------------------------------------------------------------- /src/test/suite/Utils/example.test.rules: -------------------------------------------------------------------------------- 1 | one.two.three 2 | one.two.three 3 | one/two(three 4 | one two three 5 | one two.three four -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/rules/allow-comment.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | allow read, write: if true; // test comment 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/rules/root-comment.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | // test comment 3 | service cloud.firestore { 4 | match /databases/{database}/documents { 5 | allow read, write: if true; 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/rules/allow-without-condition-comment.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | allow read, write; // test comment 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/rules/service-comment.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | // test comment 4 | match /databases/{database}/documents { 5 | allow read, write: if true; 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/rules/matcher-comment.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | // test comment 5 | allow read, write: if true; 6 | } 7 | } -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { FirestoreHoverProvider } from "./FirestoreHoverProvider"; 2 | export { FirestoreCompletionProvider } from "./FirestoreCompletionProvider"; 3 | export { FirestoreFormattingProvider } from "./FirestoreFormattingProvider"; 4 | -------------------------------------------------------------------------------- /src/test/suite/autoformatter/functionNamedMatches.formatted.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | function isSignedIn() { 4 | return request.auth != null; 5 | } 6 | 7 | function isWritingImage() { 8 | return request.resource.contentType.matches('image/.*'); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/test/suite/autoformatter/functionNamedMatches.test.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | function isSignedIn() { 4 | return request.auth != null; 5 | } 6 | 7 | function isWritingImage() { 8 | return request.resource.contentType.matches('image/.*'); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import { before } from 'mocha'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | before(() => { 10 | vscode.window.showInformationMessage('Start all tests.'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | ], 12 | rules: { 13 | "@typescript-eslint/no-use-before-define": ["off"] 14 | } 15 | }; -------------------------------------------------------------------------------- /dev-troubleshooting.md: -------------------------------------------------------------------------------- 1 | Development Troubleshooting 2 | =========================== 3 | 4 | 5 | Running the Extension 6 | --------------------- 7 | 8 | When the Extension cannot be loaded due to an error regarding the wrong NODE_MODULE_VERSION, 9 | look at https://sneezry.com/vscode-version-watcher/ and use `./node_modules/.bin/electron-rebuild -v ` 10 | to rebuild the binaries for the correct electron version. -------------------------------------------------------------------------------- /src/test/suite/autoformatter/matcherNoSemicolon.formatted.rules: -------------------------------------------------------------------------------- 1 | rules_version ='2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | 5 | function isVizmoAdmin() { 6 | return authed() && request.auth.token.vizmoAdmin 7 | } 8 | 9 | //vizmo-admin rules 10 | match /companies/{document=**} { 11 | allow read: if true//isVizmoAdmin() 12 | allow write: if isVizmoAdmin() 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/suite/autoformatter/matcherNoSemicolon.test.rules: -------------------------------------------------------------------------------- 1 | rules_version ='2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | 5 | function isVizmoAdmin() { 6 | return authed() && request.auth.token.vizmoAdmin 7 | } 8 | 9 | //vizmo-admin rules 10 | match /companies/{document=**} { 11 | allow read: if true//isVizmoAdmin() 12 | allow write: if isVizmoAdmin() 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/test/suite/language.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | test('the language grammar file can be parsed', () => { 6 | const grammarPath = path.resolve(__dirname + '/../../../syntaxes/firestorerules.tmLanguage.json'); 7 | const grammarJSON = fs.readFileSync(grammarPath).toString(); 8 | 9 | const result = JSON.parse(grammarJSON); 10 | 11 | assert.equal(true, result !== null && typeof result === 'object'); 12 | }); 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "firestorerules.usePrettierFormatter": false 12 | } -------------------------------------------------------------------------------- /src/providers/FirestoreHoverProvider.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position, HoverProvider, Hover, ProviderResult } from 'vscode'; 2 | import { getDocForToken } from '../Documentation'; 3 | import { getTokenUntil } from '../utils'; 4 | 5 | export class FirestoreHoverProvider implements HoverProvider { 6 | provideHover(document: TextDocument, position: Position): ProviderResult { 7 | const markedWord = document.getText(document.getWordRangeAtPosition(position)); 8 | return new Hover(getDocForToken(getTokenUntil(document, position), markedWord) || ''); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, languages } from 'vscode'; 2 | import { FirestoreHoverProvider, FirestoreCompletionProvider, FirestoreFormattingProvider } from './providers'; 3 | 4 | export function activate(context: ExtensionContext): void { 5 | context.subscriptions.push(languages.registerHoverProvider('firestorerules', new FirestoreHoverProvider())); 6 | context.subscriptions.push(languages.registerCompletionItemProvider('firestorerules', new FirestoreCompletionProvider())); 7 | context.subscriptions.push(languages.registerDocumentFormattingEditProvider('firestorerules', new FirestoreFormattingProvider())); 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test", 20 | "out", 21 | "*.config.js", 22 | "test" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /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, launchArgs: ['--disable-extensions'] }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/runTestLegacy.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, version: '1.37.0', launchArgs: ['--disable-extensions'] }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | 6 | install: 7 | - | 8 | if [ $TRAVIS_OS_NAME == "linux" ]; then 9 | export DISPLAY=':99.0' 10 | /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 11 | fi 12 | 13 | jobs: 14 | include: 15 | - stage: build 16 | script: 17 | - | 18 | echo ">>>> Build Extension" 19 | yarn && yarn build 20 | - stage: lint 21 | script: 22 | - | 23 | echo ">>>> Lint Extension" 24 | yarn lint 25 | - stage: test 26 | name: "Test" 27 | script: 28 | - | 29 | echo ">>>> Test Extension" 30 | yarn test 31 | - name: "Legacy Test" 32 | script: 33 | - | 34 | echo ">>>> Test Extension" 35 | yarn test-legacy 36 | 37 | after_success: 38 | - bash <(curl -s https://codecov.io/bash) 39 | 40 | cache: yarn -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as 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/documentation/types.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, MarkdownString } from 'vscode'; 2 | 3 | export const scopes = ['source.firebase', 4 | 'comment.line', 5 | 'meta.root.fs', 6 | 'meta.matcher.fs', 7 | 'string.unquoted.fs', 8 | 'meta.function.fs', 9 | 'meta.function.expression.fs', 10 | 'meta.allow.fs', 11 | 'meta.allow.head.fs', 12 | 'meta.allow.scope.fs', 13 | 'meta.allow.body.fs', 14 | 'meta.allow.body.if.fs', 15 | 'meta.functioncall.fs', 16 | 'string.quoted.firestorerules'] as const; 17 | export type Scope = typeof scopes[number]; 18 | 19 | export type FlatDoc = { [name: string]: string | MarkdownString }; 20 | 21 | export interface DocumentationValue { 22 | header?: string; 23 | doc: string | MarkdownString; 24 | kind?: CompletionItemKind; 25 | childs?: Documentation; 26 | scopes?: Scope[]; 27 | } 28 | 29 | export type Documentation = { [name: string]: DocumentationValue }; -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "/*", "*/" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"] 21 | ], 22 | // symbols that that can be used to surround a selection 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[", "]"], 26 | ["(", ")"], 27 | ["\"", "\""], 28 | ["'", "'"] 29 | ] 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Christoph Flick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--disable-extensions", 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ], 20 | "preLaunchTask": "npm: watch" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "--disable-extensions", 29 | "--extensionDevelopmentPath=${workspaceFolder}", 30 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 31 | ], 32 | "outFiles": [ 33 | "${workspaceFolder}/out/test/**/*.js" 34 | ], 35 | "preLaunchTask": "npm: watch" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Range, Position } from 'vscode'; 2 | 3 | export const getWholeToken = (document: TextDocument, position: Position): string => { 4 | let token = ''; 5 | let tmpPosition = position; 6 | 7 | //find start position 8 | while (tmpPosition.character > 0 && document.getText(new Range(tmpPosition, tmpPosition.translate(0, -1))).match(/[a-zA-Z0-9.]/)) { 9 | tmpPosition = tmpPosition.translate(0, -1); 10 | } 11 | 12 | // till end position 13 | while (document.getText(new Range(tmpPosition, tmpPosition.translate(0, 1))).match(/[a-zA-Z0-9.]/)) { 14 | token += document.getText(new Range(tmpPosition, tmpPosition.translate(0, 1))); 15 | tmpPosition = tmpPosition.translate(0, 1); 16 | } 17 | 18 | return token; 19 | }; 20 | 21 | export const getTokenUntil = (document: TextDocument, position: Position): string => { 22 | const wordRange = document.getWordRangeAtPosition(position); 23 | if (!wordRange) { 24 | return ''; 25 | } 26 | 27 | let token = document.getText(wordRange); 28 | let tmpPosition = wordRange.start; 29 | 30 | //find start position 31 | while (tmpPosition.character > 0 && document.getText(new Range(tmpPosition, tmpPosition.translate(0, -1))).match(/[a-zA-Z0-9.]/)) { 32 | token = document.getText(new Range(tmpPosition, tmpPosition.translate(0, -1))) + token; 33 | tmpPosition = tmpPosition.translate(0, -1); 34 | } 35 | 36 | return token; 37 | }; -------------------------------------------------------------------------------- /src/providers/FirestoreCompletionProvider.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, Position, TextDocument } from 'vscode'; 2 | import { getPotentialDocForPartialScoped } from '../Documentation'; 3 | import { tokenize } from '../utils/textmate/textmate'; 4 | 5 | export class FirestoreCompletionProvider implements CompletionItemProvider { 6 | async provideCompletionItems(document: TextDocument, position: Position): Promise { 7 | let results: CompletionItem[] = []; 8 | 9 | try { 10 | const tokenizedDoc = await tokenize(document); 11 | 12 | const lineTokens = tokenizedDoc[position.line]; 13 | const currentToken = lineTokens.find(token => token.range.contains(position)); 14 | 15 | if (!currentToken) { 16 | return []; 17 | } 18 | 19 | console.log(currentToken.scopes); 20 | 21 | let partial = document.getText(currentToken.range).trim(); 22 | 23 | if (partial.includes(' ')) { 24 | partial = partial.split(' ').pop() || ''; 25 | } 26 | 27 | results = getPotentialDocForPartialScoped(partial, currentToken.scopes.slice(-1)[0]) 28 | .map(doc => { 29 | const docName = doc[0]; 30 | const item = new CompletionItem(typeof docName === 'string' ? docName : docName.value, CompletionItemKind.Class); 31 | item.documentation = doc[1]; 32 | return item; 33 | }); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | 38 | return results; 39 | } 40 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master, ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 20 * * 2' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | # ℹ️ Command-line programs to run using the OS shell. 43 | # 📚 https://git.io/JvXDl 44 | 45 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 46 | # and modify them (or add more) to build your code if your project 47 | # uses a compiled language 48 | 49 | #- run: | 50 | # make bootstrap 51 | # make release 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v1 55 | -------------------------------------------------------------------------------- /src/utils/textmate/scope-info.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Range, TextDocument } from 'vscode'; 3 | 4 | /** 5 | * A grammar 6 | */ 7 | export interface IGrammar { 8 | /** 9 | * Tokenize `lineText` using previous line state `prevState`. 10 | */ 11 | tokenizeLine(lineText: string, prevState: StackElement): ITokenizeLineResult; 12 | } 13 | export interface ITokenizeLineResult { 14 | readonly tokens: IToken[]; 15 | /** 16 | * The `prevState` to be passed on to the next line tokenization. 17 | */ 18 | readonly ruleStack: StackElement; 19 | } 20 | export interface IToken { 21 | startIndex: number; 22 | readonly endIndex: number; 23 | readonly scopes: string[]; 24 | } 25 | /** 26 | * **IMPORTANT** - Immutable! 27 | */ 28 | export interface StackElement { 29 | equals(other: StackElement): boolean; 30 | } 31 | 32 | 33 | export class Token { 34 | scopes: string[]; 35 | range: Range; 36 | document: vscode.TextDocument; 37 | 38 | static create(token: IToken, line: number, document: vscode.TextDocument): Token { 39 | return new Token(token.scopes, 40 | new Range(line, token.startIndex, line, token.endIndex), document); 41 | } 42 | 43 | constructor(scopes: string[], range: Range, document: TextDocument) { 44 | this.scopes = scopes; 45 | this.document = document; 46 | this.range = range; 47 | } 48 | 49 | inScope(str: string): boolean { 50 | for (const scope of this.scopes) { 51 | if (scope.indexOf(str) !== -1) { 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | text(): string { 59 | return this.document.getText(this.range); 60 | } 61 | } 62 | 63 | export interface ScopeInfoAPI { 64 | getScopeAt(document: vscode.TextDocument, position: vscode.Position): Token | null; 65 | getGrammar(scopeName: string): Promise; 66 | getScopeForLanguage(language: string): string | null; 67 | } 68 | -------------------------------------------------------------------------------- /src/test/suite/syntax-highlighting/comments.test.ts: -------------------------------------------------------------------------------- 1 | import { describe } from 'mocha'; 2 | import * as vscode from 'vscode'; 3 | import { tokenize } from '../../../utils/textmate/textmate'; 4 | import { expect } from 'chai'; 5 | import { Token } from '../../../utils/textmate/scope-info'; 6 | import * as flatMap from 'array.prototype.flatmap'; 7 | 8 | const TEST_RULES_PATH = __dirname + '/../../../../src/test/suite/syntax-highlighting/rules'; 9 | 10 | const flatten = (tokens: Token[]): string[] => flatMap(tokens, (t: Token) => t.scopes); 11 | 12 | describe('the should highlight comments', () => { 13 | test('at root level', async () => { 14 | const document = await vscode.workspace.openTextDocument(TEST_RULES_PATH + '/root-comment.rules'); 15 | 16 | const tokenizedLines = await tokenize(document); 17 | 18 | expect(flatten(tokenizedLines[1])).contains('comment.line'); 19 | }); 20 | 21 | test('at service level', async () => { 22 | const document = await vscode.workspace.openTextDocument(TEST_RULES_PATH + '/service-comment.rules'); 23 | 24 | const tokenizedLines = await tokenize(document); 25 | 26 | expect(flatten(tokenizedLines[2])).contains('comment.line'); 27 | }); 28 | 29 | test('at matcher level', async () => { 30 | const document = await vscode.workspace.openTextDocument(TEST_RULES_PATH + '/matcher-comment.rules'); 31 | 32 | const tokenizedLines = await tokenize(document); 33 | 34 | expect(flatten(tokenizedLines[3])).contains('comment.line'); 35 | }); 36 | 37 | test('at allow level', async () => { 38 | const document = await vscode.workspace.openTextDocument(TEST_RULES_PATH + '/allow-comment.rules'); 39 | 40 | const tokenizedLines = await tokenize(document); 41 | 42 | expect(flatten(tokenizedLines[3])).contains('comment.line'); 43 | }); 44 | 45 | test('at allow level without condition', async () => { 46 | const document = await vscode.workspace.openTextDocument(TEST_RULES_PATH + '/allow-without-condition-comment.rules'); 47 | 48 | const tokenizedLines = await tokenize(document); 49 | 50 | expect(flatten(tokenizedLines[3])).contains('comment.line'); 51 | }); 52 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Installs](https://vsmarketplacebadge.apphb.com/installs-short/ChFlick.firecode.svg)](https://marketplace.visualstudio.com/items?itemName=ChFlick.firecode) 2 | [![Visual Studio Marketplace](https://vsmarketplacebadge.apphb.com/version/ChFlick.firecode.svg)](https://marketplace.visualstudio.com/items?itemName=ChFlick.firecode) 3 | [![Build Status](https://travis-ci.org/ChFlick/firecode.svg?branch=master)](https://travis-ci.org/ChFlick/firecode) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/a06d165d57630120c00d/maintainability)](https://codeclimate.com/github/ChFlick/firecode/maintainability) 5 | [![Dependencies](https://david-dm.org/ChFlick/firecode.svg)](https://david-dm.org/ChFlick/firecode) 6 | [![devDependencies Status](https://david-dm.org/ChFlick/firecode/dev-status.svg)](https://david-dm.org/ChFlick/firecode?type=dev) 7 | 8 | # Firestore 9 | 10 | Firestore security rule support for Visual Studio Code. 11 | 12 | Works for `.rule` and `.rules` files. 13 | 14 | ## Features 15 | 16 | ### Syntax Highlighting 17 | ![Syntax Highlighting](./resources/syntax-highlighting.png) 18 | 19 | ### Mouseover Information 20 | ![Mouseover Information](./resources/mouseover-info.gif) 21 | 22 | ### Autocomplete Suggestions 23 | ![Autocompletion](./resources/autocomplete.gif) 24 | 25 | ### Autoformatting 26 | ![Autoformatting](./resources/autoformat.gif) 27 | 28 | Only works for valid rules and currently only formats the indentation. 29 | 30 | **Experimental:** 31 | Version 1.3.0 intoduced a new formatter option which can be enabled in the configuration, `firestorerules.usePrettierFormatter`. 32 | When the option is enabled, the project uses [prettier](https://prettier.io/) and the [prettier-plugin-firestore-rules](https://github.com/ChFlick/prettier-plugin-firestore-rules) to format the firestore rules. 33 | 34 | ## Known Issues 35 | 36 | * The documentation of the get(/path/) function is currently not correct 37 | 38 | This extension is still very fresh and under development, so if you have any issues, please [report them on GitHub](https://github.com/ChFlick/firecode/issues). 39 | 40 | ## Development 41 | 42 | I'm working on improving the prettier-plugin-firestore-rules formatter as well as the corresponding [firestore rules parser (WIP)](https://github.com/ChFlick/prettier-plugin-firestore-rules). 43 | 44 | When the parser is in a proper shape, it will be included to provide validation of the firestore rules. 45 | -------------------------------------------------------------------------------- /src/test/suite/autoformatter/complex.formatted.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | function hasValue(value, uid, carId) { 5 | // a comment 6 | let someValue = true || request.resource.data.size() == 2; 7 | return get(/databases/$(database)/documents/someData/$(uid)/subCollection/$(carId)).data.someData.hasAll([value]) 8 | } 9 | 10 | match /someData/{targetUserId} { 11 | allow write: if false; 12 | allow read: if request.auth.uid == targetUserId 13 | match /subCollection/{carId} { 14 | allow read: if hasValue('bla', request.auth.uid, carId); 15 | allow update: if false; // Some comment 16 | // another comment 17 | } 18 | } 19 | 20 | match /users/{userId} { 21 | allow write, read: if false; 22 | } 23 | 24 | match /cars/{carId} { 25 | match /roles/{roleId} { 26 | allow delete: if get(/databases/$(database)/documents/configurations/someData).data.values.hasAll(request.resource.data.someData) 27 | && request.resource.data.keys().hasOnly(['name', 'someData']) 28 | && request.resource.data.size() == 2 29 | && request.resource.data.name is string 30 | && request.resource.data.someData is list 31 | && hasValue('bla', request.auth.uid, carId); 32 | 33 | allow read: if hasValue('bla', request.auth.uid, carId); 34 | } 35 | 36 | match /oneLevel/{one} { 37 | match /twoLevel/{two} { 38 | match /threeLevel/{three} { 39 | allow write, read: if request.resource.data.asdf is int && 40 | request.resource.data.asdf is 333; 41 | } 42 | } 43 | } 44 | 45 | match /store/current/{doc=**} { 46 | allow read: if request.auth.uid != null; 47 | 48 | match /slots/{slotId} { 49 | allow write: if hasValue('writeSLots', request.auth.uid, carId) 50 | && request.resource.data.keys().hasOnly(['rank', 'name', 'description', 'imageUrl', 'timeSlot']) 51 | && request.resource.data.rank is int 52 | && request.resource.data.name is string 53 | && request.resource.data.description is string 54 | && request.resource.data.imageUrl is string path 55 | && request.resource.data.timeSlot.from is timestamp 56 | && request.resource.data.timeSlot.to is timestamp 57 | && request.resource.data.timeSlot.to > request.resource.data.timeSlot.from; 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/suite/autoformatter/autoformatter.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { describe } from 'mocha'; 3 | import * as vscode from 'vscode'; 4 | 5 | describe('the autoformatter', () => { 6 | test('should format rules with matchers with no semicolon', async () => { 7 | const formattedDocument = await vscode.workspace 8 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/matcherNoSemicolon.formatted.rules'); 9 | const document = await vscode.workspace 10 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/matcherNoSemicolon.test.rules'); 11 | 12 | const formattedResult = await getDocumentFormatted(document); 13 | 14 | assert.strictEqual(formattedResult, formattedDocument.getText()); 15 | }); 16 | 17 | test('should format some complex rules', async () => { 18 | const formattedDocument = await vscode.workspace 19 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/complex.formatted.rules'); 20 | const document = await vscode.workspace 21 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/complex.test.rules'); 22 | 23 | const formattedResult = await getDocumentFormatted(document); 24 | 25 | assert.strictEqual(formattedResult, formattedDocument.getText()); 26 | }); 27 | 28 | test('should format a function named "matches" correctly', async () => { 29 | const formattedDocument = await vscode.workspace 30 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/functionNamedMatches.formatted.rules'); 31 | const document = await vscode.workspace 32 | .openTextDocument(__dirname + '/../../../../src/test/suite/autoformatter/functionNamedMatches.test.rules'); 33 | 34 | const formattedResult = await getDocumentFormatted(document); 35 | 36 | assert.strictEqual(formattedResult, formattedDocument.getText()); 37 | }); 38 | }); 39 | 40 | async function getDocumentFormatted(document: vscode.TextDocument) { 41 | await vscode.workspace.getConfiguration("firestorerules").update("usePrettierFormatter", false); 42 | 43 | const editor = await vscode.window.showTextDocument(document); 44 | const formatEdits = await vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', document.uri, { tabSize: 2, insertSpaces: true }) as vscode.TextEdit[]; 45 | await editor.edit(editBuilder => formatEdits.forEach(edit => editBuilder.replace(edit.range, edit.newText))); 46 | const formattedResult = editor.document.getText(); 47 | return formattedResult; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/documentation/keywordDocumentation.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownString } from "vscode"; 2 | import { Documentation } from "./types"; 3 | 4 | // Mostly extracted from https://firebase.google.com/docs/reference/rules/index-all 5 | export const keywordDoc: Readonly = { 6 | match: { 7 | doc: `A \`match\` block declares a \`path\` pattern that is matched against the path for the requested operation (the incoming \`request.path\`). 8 | The body of the \`match\` must have one or more nested \`match\` blocks, \`allow\` statements, or \`function\` declarations. 9 | The path in nested \`match\` blocks is relative to the path in the parent \`match\` block.`, 10 | scopes: ['meta.matcher.fs', 'meta.root.fs'], 11 | }, 12 | allow: { 13 | doc: new MarkdownString( 14 | `allow a request if the following condition evaluates to \`true\`` + '\n\n' + 15 | `The following methods are possible: 16 | * read 17 | * get 18 | * list 19 | * write 20 | * create 21 | * update 22 | * delete`), 23 | scopes: ['meta.matcher.fs'], 24 | }, 25 | service: { 26 | doc: 'contains one or more `match` blocks with `allow` statements that provide conditions granting access to requests', 27 | scopes: ['source.firebase'], 28 | }, 29 | read: { 30 | doc: 'Any type of read request. Equals `get` and `list`', 31 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 32 | }, 33 | get: { 34 | doc: 'Read requests for single documents or files.', 35 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 36 | }, 37 | list: { 38 | doc: 'Read requests for queries and collections.', 39 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 40 | }, 41 | write: { 42 | doc: 'Any type of write request. Equals `create`, `update`, and `delete`', 43 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 44 | }, 45 | create: { 46 | doc: 'Write new documents or files', 47 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 48 | }, 49 | update: { 50 | doc: 'Write to existing documents or files', 51 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 52 | }, 53 | delete: { 54 | doc: 'Delete data', 55 | scopes: ['meta.allow.head.fs', 'meta.allow.scope.fs'], 56 | }, 57 | return: { 58 | doc: 'returns the value', 59 | scopes: ['meta.function.fs'], 60 | }, 61 | if: { 62 | doc: '', 63 | scopes: ['meta.allow.body.fs'] 64 | } 65 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "firecode" extension will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | 7 | ## [1.3.0] - 2020-09-15 8 | ### Added 9 | - Experimental support for formatting using [prettier](https://prettier.io/) and [prettier-plugin-firestore-rules](https://github.com/ChFlick/prettier-plugin-firestore-rules). 10 | - Configuration flag `firestorerules.usePrettierFormatter` to switch between the prettier and default formatter. 11 | 12 | ## [1.2.11] - 2020-09-15 13 | ### Fixed 14 | - Formatting whitespace on empty lines 15 | 16 | ## [1.2.10] - 2020-07-08 17 | ### Fixed 18 | - Formatting when the line contaned the words match, allow, or function not as keywords. 19 | 20 | ## [1.2.9] - 2020-07-06 21 | ### Fixed 22 | - Syntax highlighting for comments in the service scope. 23 | - Syntax highlighting for allow statements without if/condition, like `allow read;`. 24 | 25 | ## [1.2.8] - 2020-06-26 26 | ### Fixed 27 | - Syntax highlighting for functions outside of the matcher scope. 28 | 29 | ## [1.2.7] - 2020-06-25 30 | - Not properly published - did not change anything. 31 | 32 | ## [1.2.6] - 2020-06-02 33 | ### Fixed 34 | - Textmate/Oniguruma issues in recent VS Code versions. 35 | 36 | ## [1.2.5] - 2020-05-12 37 | ### Fixed 38 | - The extension possibly not working at all due to rebuild issues. 39 | 40 | ## [1.2.4] - 2020-05-12 41 | ### Added 42 | - Support for firebase storage rules top-level `service firebase.storage` 43 | 44 | ## [1.2.3] - 2020-04-19 45 | ### Fixed 46 | - **let** variable declaration in function bodies. 47 | - Comments in function bodies. 48 | - Indentation in function bodies. 49 | 50 | ## [1.2.2] - 2020-02-25 51 | ### Fixed 52 | - Whitespaces in `rules_version = '2';`. 53 | - The wrong detection of the "is" operator in a word (eg isSomething). 54 | - Comments in allow expressions. 55 | - End of allow-statements colliding with the end of match-statements when not using semicolons. 56 | 57 | ## [1.2.1] - 2020-02-01 58 | ### Fixed 59 | - Basic autoformatting on windows. 60 | 61 | ## [1.2.0] - 2020-01-26 62 | ### Added 63 | - Basic autoformatting option. 64 | 65 | ## [1.1.4] - 2019-11-30 66 | ### Fixed 67 | - Grammar bugfix. 68 | 69 | ## [1.1.2] - 2019-10-09 70 | ### Changed 71 | - Some internal improvements. 72 | - Updated README to show the autocompletion. 73 | 74 | ## [1.1.1] - 2019-10-05 75 | ### Added 76 | - First draft of the completion support. 77 | 78 | ## [1.0.1] - 2019-08-21 79 | ### Added 80 | - Improve hover information support by creating a tree structure to get the correct context information. 81 | 82 | ## [1.0.0] - 2019-08-17 83 | - Initial release. 84 | 85 | ### Added 86 | - Syntax Highlighting support. 87 | - Hover Information support. 88 | -------------------------------------------------------------------------------- /src/test/suite/autoformatter/complex.test.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | function hasValue(value, uid, carId) { 5 | // a comment 6 | let someValue = true || request.resource.data.size() == 2; 7 | return get(/databases/$(database)/documents/someData/$(uid)/subCollection/$(carId)).data.someData.hasAll([value]) 8 | } 9 | 10 | match /someData/{targetUserId} { 11 | allow write: if false; 12 | allow read: if request.auth.uid == targetUserId 13 | match /subCollection/{carId} { 14 | allow read: if hasValue('bla', request.auth.uid, carId); 15 | allow update: if false; // Some comment 16 | // another comment 17 | } 18 | } 19 | 20 | match /users/{userId} { 21 | allow write, read: if false; 22 | } 23 | 24 | match /cars/{carId} { 25 | match /roles/{roleId} { 26 | allow delete: if get(/databases/$(database)/documents/configurations/someData).data.values.hasAll(request.resource.data.someData) 27 | && request.resource.data.keys().hasOnly(['name', 'someData']) 28 | && request.resource.data.size() == 2 29 | && request.resource.data.name is string 30 | && request.resource.data.someData is list 31 | && hasValue('bla', request.auth.uid, carId); 32 | 33 | allow read: if hasValue('bla', request.auth.uid, carId); 34 | } 35 | 36 | match /oneLevel/{one} { 37 | match /twoLevel/{two} { 38 | match /threeLevel/{three} { 39 | allow write, read: if request.resource.data.asdf is int && 40 | request.resource.data.asdf is 333; 41 | } 42 | } 43 | } 44 | 45 | match /store/current/{doc=**} { 46 | allow read: if request.auth.uid != null; 47 | 48 | match /slots/{slotId} { 49 | allow write: if hasValue('writeSLots', request.auth.uid, carId) 50 | && request.resource.data.keys().hasOnly(['rank', 'name', 'description', 'imageUrl', 'timeSlot']) 51 | && request.resource.data.rank is int 52 | && request.resource.data.name is string 53 | && request.resource.data.description is string 54 | && request.resource.data.imageUrl is string path 55 | && request.resource.data.timeSlot.from is timestamp 56 | && request.resource.data.timeSlot.to is timestamp 57 | && request.resource.data.timeSlot.to > request.resource.data.timeSlot.from; 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/example.rules: -------------------------------------------------------------------------------- 1 | // Top rule 2 | 3 | rules_version = '2'; 4 | function rootFunction() { 5 | return true; 6 | } 7 | // test 8 | service cloud.firestore { 9 | function serviceFunction() { 10 | return true; 11 | } 12 | // test 13 | match /databases/{database}/documents { 14 | function hasValue(value, uid, carId) { 15 | return get(/databases/$(database)/documents/someData/$(uid)/subCollection/$(carId)).data.someData.hasAll([value]) 16 | } 17 | 18 | match /someData/{targetUserId} { 19 | allow write: if false; 20 | allow read: if request.auth.uid == targetUserId 21 | match /subCollection/{carId} { 22 | allow read: if hasValue('bla', request.auth.uid, carId); 23 | allow update; // Some comment 24 | allow delete: if false; 25 | // another comment 26 | } 27 | } 28 | 29 | match /users/{userId} { 30 | allow write, read: if false; 31 | } 32 | 33 | match /cars/{carId} { 34 | match /roles/{roleId} { 35 | allow delete: if get(/databases/$(database)/documents/configurations/someData).data.values.hasAll(request.resource.data.someData) 36 | && request.resource.data.keys().hasOnly(['name', 'someData']) 37 | && request.resource.data.size() == 2 38 | && request.resource.data.name is string 39 | && request.resource.data.someData is list 40 | && hasValue('bla', request.auth.uid, carId); 41 | 42 | allow read: if hasValue('bla', request.auth.uid, carId); 43 | } 44 | 45 | match /oneLevel/{one} { 46 | match /twoLevel/{two} { 47 | match /threeLevel/{three} { 48 | allow write, read: if request.resource.data.asdf is int && 49 | request.resource.data.asdf == 333; 50 | } 51 | } 52 | } 53 | 54 | match /store/current/{doc=**} { 55 | allow read: if request.auth.uid != null; 56 | 57 | match /slots/{slotId} { 58 | allow write: if hasValue('writeSLots', request.auth.uid, carId) 59 | && request.resource.data.keys().hasOnly(['rank', 'name', 'description', 'imageUrl', 'timeSlot']) 60 | && request.resource.data.rank is int 61 | && request.resource.data.name is string 62 | && request.resource.data.description is string 63 | && request.resource.data.imageUrl is path 64 | && request.resource.data.timeSlot.from is timestamp 65 | && request.resource.data.timeSlot.to is timestamp 66 | && request.resource.data.timeSlot.to > request.resource.data.timeSlot.from; 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/utils/textmate/textmate.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import { env, TextDocument, Range } from 'vscode'; 4 | import { Token, IToken } from './scope-info'; 5 | import * as textmate from 'vscode-textmate'; 6 | 7 | type TextmateType = typeof textmate; 8 | 9 | /** 10 | * Returns a node module installed with VSCode, or null if it fails. 11 | */ 12 | function getCoreNodeModule(moduleName: string) { 13 | try { 14 | return require(`${env.appRoot}/node_modules.asar/${moduleName}`); 15 | } catch (err) { 16 | // do nothing 17 | } 18 | 19 | try { 20 | return require(`${env.appRoot}/node_modules/${moduleName}`); 21 | } catch (err) { 22 | // do nothing 23 | } 24 | 25 | return null; 26 | } 27 | 28 | function getOnigWasmBin() { 29 | try { 30 | return fs.readFileSync(`${env.appRoot}/node_modules.asar/vscode-oniguruma/release/onig.wasm`).buffer; 31 | } catch (err) { 32 | // do nothing 33 | } 34 | 35 | try { 36 | return fs.readFileSync(`${env.appRoot}/node_modules/vscode-oniguruma/release/onig.wasm`).buffer; 37 | } catch (err) { 38 | // do nothing 39 | } 40 | 41 | console.error("Could not load the onig.wasm"); 42 | 43 | return null; 44 | } 45 | 46 | async function getRegistry(tm: TextmateType) { 47 | const onigurumaModule = getCoreNodeModule('vscode-oniguruma'); 48 | 49 | if(onigurumaModule) { 50 | await onigurumaModule.loadWASM(getOnigWasmBin()); 51 | 52 | return new tm.Registry({ 53 | onigLib: Promise.resolve({ 54 | createOnigScanner: (sources: string[]) => new onigurumaModule.OnigScanner(sources), 55 | createOnigString: (str: string) => new onigurumaModule.OnigString(str) 56 | }), 57 | loadGrammar: async () => null, 58 | }); 59 | } 60 | 61 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 62 | // @ts-ignore 63 | return new tm.Registry(); 64 | } 65 | 66 | const grammarPath = path.resolve(__dirname + '/../../../syntaxes/firestorerules.tmLanguage.json'); 67 | 68 | let grammar: textmate.IGrammar; 69 | async function getGrammar() { 70 | if (grammar) { 71 | return grammar; 72 | } 73 | 74 | const tm: TextmateType = await getCoreNodeModule('vscode-textmate'); 75 | const registry = await getRegistry(tm); 76 | const grammarFile = fs.readFileSync(grammarPath).toString(); 77 | grammar = await registry.addGrammar(tm.parseRawGrammar(grammarFile, grammarPath)); 78 | 79 | return grammar; 80 | } 81 | 82 | export async function tokenize(document: TextDocument): Promise { 83 | const grammar = await getGrammar(); 84 | 85 | let ruleStack: textmate.StackElement | null = null; 86 | const tokens: Token[][] = []; 87 | for (let i = 0; i < document.lineCount; i++) { 88 | const line = document.getText(new Range(i, 0, i + 1, 0)); 89 | const r = grammar.tokenizeLine(line, ruleStack); 90 | tokens.push(r.tokens.map((v: IToken) => Token.create(v, i, document))); 91 | ruleStack = r.ruleStack; 92 | } 93 | return tokens; 94 | } 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firecode", 3 | "displayName": "Firestore Rules", 4 | "description": "Firestore Security Rules Syntax Highlighting and Suggestions", 5 | "publisher": "ChFlick", 6 | "author": { 7 | "name": "Christoph Flick", 8 | "url": "https://christophflick.de/", 9 | "email": "hi+removethisforspamreasons@christophflick.de" 10 | }, 11 | "license": "MIT", 12 | "icon": "resources/firecode.png", 13 | "version": "1.2.11", 14 | "homepage": "https://github.com/ChFlick/firecode/blob/master/README.md", 15 | "keywords": [ 16 | "firebase", 17 | "firestore", 18 | "rules" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/ChFlick/firecode" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/ChFlick/firecode/issues" 26 | }, 27 | "galleryBanner": { 28 | "color": "#FFCA49", 29 | "theme": "light" 30 | }, 31 | "engines": { 32 | "vscode": "^1.2.0" 33 | }, 34 | "categories": [ 35 | "Programming Languages" 36 | ], 37 | "activationEvents": [ 38 | "onLanguage:firestorerules" 39 | ], 40 | "main": "./out/extension.js", 41 | "contributes": { 42 | "languages": [ 43 | { 44 | "id": "firestorerules", 45 | "aliases": [ 46 | "Firestore Rules", 47 | "firestore" 48 | ], 49 | "extensions": [ 50 | "rules", 51 | "rule" 52 | ], 53 | "configuration": "./language-configuration.json" 54 | } 55 | ], 56 | "grammars": [ 57 | { 58 | "language": "firestorerules", 59 | "scopeName": "source.firebase", 60 | "path": "./syntaxes/firestorerules.tmLanguage.json" 61 | } 62 | ], 63 | "configuration": { 64 | "title": "Firestore Rules", 65 | "properties": { 66 | "firestorerules.usePrettierFormatter": { 67 | "type": "boolean", 68 | "default": false, 69 | "markdownDescription": "Use the new [firestore prettier plugin](https://github.com/ChFlick/prettier-plugin-firestore-rules) for formatting. **Experimental**" 70 | } 71 | } 72 | } 73 | }, 74 | "scripts": { 75 | "vscode:prepublish": "yarn lint && yarn build && yarn test-legacy && yarn test", 76 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx", 77 | "build": "tsc -p ./", 78 | "watch": "tsc -watch -p ./", 79 | "pretest": "yarn build", 80 | "test": "yarn pretest && node ./out/test/runTest.js", 81 | "test-legacy": "yarn build && node ./out/test/runTestLegacy.js" 82 | }, 83 | "devDependencies": { 84 | "@types/chai": "^4.2.11", 85 | "@types/glob": "^7.1.1", 86 | "@types/mocha": "^7.0.2", 87 | "@types/node": "^13.5.0", 88 | "@types/oniguruma": "^7.0.1", 89 | "@types/vscode": "^1.2.0", 90 | "@typescript-eslint/eslint-plugin": "^3.4.0", 91 | "@typescript-eslint/parser": "^3.4.0", 92 | "chai": "^4.2.0", 93 | "eslint": "^7.3.1", 94 | "glob": "^7.1.4", 95 | "mocha": "^7.0.1", 96 | "typescript": "^3.3.1", 97 | "vscode-test": "^1.0.2", 98 | "vscode-textmate": "^5.1.1" 99 | }, 100 | "dependencies": { 101 | "@types/array.prototype.flatmap": "^1.2.1", 102 | "@types/prettier": "^2.1.1", 103 | "array.prototype.flatmap": "^1.2.3", 104 | "prettier": "^2.1.1", 105 | "prettier-plugin-firestore-rules": "^0.1.4" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/providers/FirestoreFormattingProvider.ts: -------------------------------------------------------------------------------- 1 | import { DocumentFormattingEditProvider, FormattingOptions, Position, Range, TextDocument, TextEdit, workspace } from 'vscode'; 2 | import { tokenize } from '../utils/textmate/textmate'; 3 | import * as prettier from 'prettier'; 4 | 5 | const indentationScopes = ['meta.root.fs', 'meta.matcher.fs', 'meta.function.fs']; 6 | const reduceWith = /match\s|service\s|function\s|^\s*\}\s*$/g; 7 | const concatedAndOr = /^\s*(&&|\|\|)/g; 8 | 9 | export class FirestoreFormattingProvider implements DocumentFormattingEditProvider { 10 | 11 | async provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions): Promise { 12 | if (workspace.getConfiguration("firestorerules").get("usePrettierFormatter") || false) { 13 | const text = document.getText(); 14 | 15 | const formattedText = prettier.format(text, { 16 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 17 | // @ts-ignore custom "firestore" parser is not known in typescript 18 | parser: "firestore", 19 | useTabs: !options.insertSpaces, 20 | tabWidth: options.tabSize 21 | }); 22 | const lastLineId = document.lineCount - 1; 23 | const fullRange = new Range(0, 0, lastLineId, document.lineAt(lastLineId).text.length); 24 | 25 | return [TextEdit.replace(fullRange, formattedText)]; 26 | } 27 | 28 | const results: TextEdit[] = []; 29 | 30 | try { 31 | const tokenizedDoc = await tokenize(document); 32 | tokenizedDoc.forEach((token, line) => { 33 | let numberOfIndentations = Math.max(...token.map( 34 | item => item.scopes.reduce( 35 | (count, scope) => indentationScopes.includes(scope) ? count + 1 : count, 0 36 | ) 37 | )); 38 | 39 | // Do not indent empty lines at all 40 | if (document.lineAt(line).text.trim().length === 0) { 41 | numberOfIndentations = 0; 42 | } 43 | 44 | // Do not indent match, service and closing bracket lines 45 | if (document.lineAt(line).text.match(reduceWith)) { 46 | numberOfIndentations--; 47 | } 48 | 49 | // Add an indentation step if line starts with && or || 50 | if (document.lineAt(line).text.match(concatedAndOr)) { 51 | numberOfIndentations++; 52 | } 53 | 54 | const currentNumberOfIndentationWhitespace = document.lineAt(line).text.length - document.lineAt(line).text.trimLeft().length; 55 | const indentationSize = options.tabSize || 2; 56 | const indentChar = options.insertSpaces ? ' '.repeat(indentationSize) : '\t'; 57 | 58 | results.push( 59 | TextEdit.replace( 60 | new Range(new Position(line, 0), new Position(line, currentNumberOfIndentationWhitespace)), 61 | indentChar.repeat(numberOfIndentations) 62 | ) 63 | ); 64 | }); 65 | 66 | } catch (error) { 67 | console.log(error); 68 | } 69 | 70 | return results; 71 | } 72 | } -------------------------------------------------------------------------------- /src/documentation/methodDocumentation.ts: -------------------------------------------------------------------------------- 1 | import { Documentation } from "./types"; 2 | 3 | // Mostly extracted from https://firebase.google.com/docs/reference/rules/index-all 4 | export const methodDoc: Readonly = { 5 | duration: { 6 | doc: 'Globally available duration functions. These functions are accessed using the `duration.` prefix.', 7 | childs: { 8 | abs: { 9 | header: 'abs(duration) returns rules.Duration', 10 | doc: 'Absolute value of a duration.' 11 | }, 12 | time: { 13 | header: 'time(hours, mins, secs, nanos) returns rules.Duration', 14 | doc: 'Create a duration from hours, minutes, seconds, and nanoseconds.' 15 | }, 16 | value: { 17 | header: 'value(magnitude, unit) returns rules.Duration', 18 | doc: 'Create a duration from a numeric magnitude and string unit.' 19 | }, 20 | } 21 | }, 22 | latlng: { 23 | doc: 'Globally available latitude-longitude functions. These functions are accessed using the `latlng.` prefix.', 24 | childs: { 25 | value: { 26 | header: 'value(lat, lng) returns rules.LatLng', 27 | doc: 'Create a LatLng from floating point coordinates.' 28 | } 29 | } 30 | }, 31 | exists: { 32 | header: 'exists(path) returns rules.Boolean', 33 | doc: 'Check if a document exists.' 34 | }, 35 | existsAfter: { 36 | header: 'existsAfter(path) returns rules.Boolean', 37 | doc: 'Check if a document exists, assuming the current request succeeds. Equivalent to getAfter(path) != null.' 38 | }, 39 | get: { 40 | header: 'get(path) returns rules.firestore.Resource', 41 | doc: 'Get the contents of a firestore document.' 42 | }, 43 | getAfter: { 44 | header: 'getAfter(path) returns rules.firestore.Resource', 45 | doc: 'Get the projected contents of a document. The document is returned as if the current request had succeeded. Useful for validating documents that are part of a batched write or transaction.' 46 | }, 47 | math: { 48 | doc: 'Globally available mathematical functions. These functions are accessed using the `math.` prefix and operate on numerical values.', 49 | childs: { 50 | abs: { 51 | header: 'abs(num) returns number', 52 | doc: 'Absolute value of a numeric value.' 53 | }, 54 | ceil: { 55 | header: 'ceil(num) returns rules.Integer', 56 | doc: 'Ceiling of the numeric value.' 57 | }, 58 | floor: { 59 | header: 'floor(num) returns rules.Integer', 60 | doc: 'Ceiling of the numeric value.' 61 | }, 62 | isInfinite: { 63 | header: 'isInfinite(num) returns rules.Boolean', 64 | doc: 'Test whether the value is ±∞.' 65 | }, 66 | isNaN: { 67 | header: 'isNaN(num) returns rules.Boolean', 68 | doc: 'Test whether the value is NaN.' 69 | }, 70 | round: { 71 | header: 'round(num) returns rules.Integer', 72 | doc: 'Round the input value to the nearest int.' 73 | } 74 | } 75 | }, 76 | timestamp: { 77 | doc: 'Globally available timestamp functions. These functions are accessed using the `timestamp.` prefix.', 78 | childs: { 79 | date: { 80 | header: 'date(year, month, day) returns rules.Timestamp', 81 | doc: 'Make a timestamp from a year, month, and day.' 82 | }, 83 | value: { 84 | header: 'value(epochMillis) returns rules.Timestamp', 85 | doc: 'Make a timestamp from an epoch time in milliseconds.' 86 | } 87 | } 88 | } 89 | }; -------------------------------------------------------------------------------- /src/utils/textmate/text-util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | // 'sticky' flag is not yet supported :( 4 | const lineEndingRE = /([^\r\n]*)(\r\n|\r|\n)?/; 5 | 6 | 7 | export interface RangeDelta { 8 | start: vscode.Position; 9 | end: vscode.Position; 10 | linesDelta: number; 11 | endCharactersDelta: number; // delta for positions on the same line as the end position 12 | } 13 | 14 | /** 15 | * @returns the Position (line, column) for the location (character position) 16 | */ 17 | function positionAt(text: string, offset: number): vscode.Position { 18 | if (offset > text.length) { offset = text.length; } 19 | let line = 0; 20 | let lastIndex = 0; 21 | // eslint-disable-next-line no-constant-condition 22 | while (true) { 23 | const match = lineEndingRE.exec(text.substring(lastIndex)); 24 | if (!match) { 25 | throw new Error(); 26 | } 27 | if (lastIndex + match[1].length >= offset) { 28 | return new vscode.Position(line, offset - lastIndex); 29 | } 30 | lastIndex += match[0].length; 31 | ++line; 32 | } 33 | } 34 | 35 | /** 36 | * @returns the lines and characters represented by the text 37 | */ 38 | export function toRangeDelta(oldRange: vscode.Range, text: string): RangeDelta { 39 | const newEnd = positionAt(text, text.length); 40 | let charsDelta; 41 | if (oldRange.start.line === oldRange.end.line) { 42 | charsDelta = newEnd.character - (oldRange.end.character - oldRange.start.character); 43 | } else { 44 | charsDelta = newEnd.character - oldRange.end.character; 45 | } 46 | return { 47 | start: oldRange.start, 48 | end: oldRange.end, 49 | linesDelta: newEnd.line - (oldRange.end.line - oldRange.start.line), 50 | endCharactersDelta: charsDelta 51 | }; 52 | } 53 | 54 | export function rangeDeltaNewRange(delta: RangeDelta): vscode.Range { 55 | let x: number; 56 | if (delta.linesDelta > 0) { 57 | x = delta.endCharactersDelta; 58 | } else if (delta.linesDelta < 0 && delta.start.line === delta.end.line + delta.linesDelta) { 59 | x = delta.end.character + delta.endCharactersDelta + delta.start.character; 60 | } else { 61 | x = delta.end.character + delta.endCharactersDelta; 62 | } return new vscode.Range(delta.start, new vscode.Position(delta.end.line + delta.linesDelta, x)); 63 | } 64 | 65 | function positionRangeDeltaTranslateStart(pos: vscode.Position, delta: RangeDelta): vscode.Position { 66 | if (pos.isBefore(delta.end)) { 67 | return pos; 68 | } 69 | 70 | return positionRangeDeltaTranslate(pos, delta); 71 | } 72 | 73 | function positionRangeDeltaTranslateEnd(pos: vscode.Position, delta: RangeDelta): vscode.Position { 74 | if (pos.isBeforeOrEqual(delta.end)) { 75 | return pos; 76 | } 77 | 78 | return positionRangeDeltaTranslate(pos, delta); 79 | } 80 | 81 | function positionRangeDeltaTranslate(pos: vscode.Position, delta: RangeDelta): vscode.Position { 82 | if (delta.end.line === pos.line) { 83 | let x = pos.character + delta.endCharactersDelta; 84 | if (delta.linesDelta > 0) { 85 | x = x - delta.end.character; 86 | } else if (delta.start.line === delta.end.line + delta.linesDelta && delta.linesDelta < 0) { 87 | x = x + delta.start.character; 88 | } 89 | 90 | return new vscode.Position(pos.line + delta.linesDelta, x); 91 | } 92 | else // if(pos.line > delta.end.line) 93 | { 94 | return new vscode.Position(pos.line + delta.linesDelta, pos.character); 95 | } 96 | } 97 | 98 | export function rangeTranslate(range: vscode.Range, delta: RangeDelta): vscode.Range { 99 | return new vscode.Range( 100 | positionRangeDeltaTranslateStart(range.start, delta), 101 | positionRangeDeltaTranslateEnd(range.end, delta) 102 | ); 103 | } 104 | 105 | export function rangeContains(range: vscode.Range, pos: vscode.Position, exclStart = false, inclEnd = false): boolean { 106 | return range.start.isBeforeOrEqual(pos) 107 | && (!exclStart || !range.start.isEqual(pos)) 108 | && ((inclEnd && range.end.isEqual(pos)) || range.end.isAfter(pos)); 109 | } 110 | 111 | export function maxPosition(x: vscode.Position, y: vscode.Position): vscode.Position { 112 | if (x.line < y.line) { 113 | return x; 114 | } if (x.line < x.line) { 115 | return y; 116 | } if (x.character < y.character) { 117 | return x; 118 | } else { 119 | return y; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Documentation.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownString, MarkedString } from 'vscode'; 2 | import { typeDoc } from './documentation/typeDocumentation'; 3 | import { methodDoc } from './documentation/methodDocumentation'; 4 | import { Documentation, FlatDoc, DocumentationValue, Scope, scopes } from './documentation/types'; 5 | import { keywordDoc } from './documentation/keywordDocumentation'; 6 | 7 | const completeDocs = { ...typeDoc, ...methodDoc, ...keywordDoc }; 8 | 9 | const flatten = (documentation: Documentation, staticValue = false): FlatDoc => { 10 | let flatDoc: FlatDoc = {}; 11 | for (const key of Object.keys(documentation)) { 12 | // With duplicate keys append content 13 | if (flatDoc[key]) { 14 | flatDoc[key] = combineStrings(flatDoc[key], createDocString(documentation[key], staticValue)); 15 | } else { 16 | flatDoc[key] = createDocString(documentation[key], staticValue); 17 | } 18 | 19 | const childs = documentation[key].childs; 20 | if (childs) { 21 | const flattenedChilds = flatten(childs, staticValue); 22 | flatDoc = combine(flatDoc, flattenedChilds); 23 | } 24 | } 25 | 26 | return flatDoc; 27 | }; 28 | 29 | const createDocString = (documentation: DocumentationValue, staticValue = false): string | MarkdownString => { 30 | if (!documentation.header) { 31 | return documentation.doc; 32 | } 33 | 34 | return new MarkdownString(staticValue ? '**static**' : '') 35 | .appendMarkdown('*' + documentation.header + '*') 36 | .appendMarkdown(' \n') 37 | .appendMarkdown(typeof documentation.doc === 'string' ? documentation.doc : documentation.doc.value); 38 | }; 39 | 40 | const combine = (...flatDocs: FlatDoc[]) => { 41 | const newFlatDoc: FlatDoc = {}; 42 | flatDocs.forEach(flatDoc => { 43 | for (const key of Object.keys(flatDoc)) { 44 | if (newFlatDoc[key]) { 45 | newFlatDoc[key] = combineStrings(newFlatDoc[key], flatDoc[key]); 46 | } else { 47 | newFlatDoc[key] = flatDoc[key]; 48 | } 49 | } 50 | }); 51 | return newFlatDoc; 52 | }; 53 | 54 | const mdStringToString = (val: string | MarkedString) => typeof val === 'string' ? val : val.value; 55 | 56 | const combineStrings = (first: string | MarkdownString, second: string | MarkdownString): MarkdownString => { 57 | const firstString = mdStringToString(first); 58 | const secondString = mdStringToString(second); 59 | 60 | return new MarkdownString(`${firstString}\n\n${secondString}`); 61 | }; 62 | 63 | // FIXME: duplicates(get!) 64 | const flatDocs = combine(flatten(typeDoc), flatten(methodDoc, true), flatten(keywordDoc)); 65 | 66 | const isInvalidToken = (token: string) => !/[a-zA-Z0-9-_.]+/.test(token); 67 | 68 | export const getDocForToken = (token: string, markedWord: string): string | MarkdownString => { 69 | if (isInvalidToken(token)) { 70 | return ''; 71 | } 72 | 73 | const parts = token.split('.'); 74 | let current: DocumentationValue = completeDocs[parts[0]]; 75 | 76 | if (!current) { 77 | return flatDocs[markedWord]; 78 | } 79 | 80 | for (const val of parts.slice(1)) { 81 | if (current.childs) { 82 | current = current.childs[val]; 83 | } else { 84 | // token not valid? lucky guess flat doc 85 | return flatDocs[markedWord]; 86 | } 87 | } 88 | 89 | return current ? current.doc : ''; 90 | }; 91 | 92 | export const getPotentialDocForPartial = (partial: string): (string | MarkdownString)[][] => { 93 | const potentialDocs = Object.keys(flatDocs) 94 | .filter(value => value.startsWith(partial)) 95 | .map(value => [value, flatDocs[value]]); 96 | 97 | return potentialDocs; 98 | }; 99 | 100 | // Suggestion: getPotentialDocForPartial(partial).inScope(scope) 101 | 102 | export const getPotentialDocForPartialScoped = (partial: string, scope: string | Scope): (string | MarkdownString)[][] => { 103 | if (isScope(scope)) { 104 | let potentialDocs = Object.keys(keywordDoc) 105 | .filter(value => (keywordDoc[value].scopes || [scope]).includes(scope)) 106 | .map(value => [value, flatDocs[value]]); 107 | 108 | if (scope === 'meta.allow.body.if.fs' || scope === 'meta.function.expression.fs') { 109 | potentialDocs = potentialDocs.concat(Object.keys(methodDoc).map(value => [value, flatDocs[value]])); 110 | } 111 | 112 | // TODO: if partial contains a dot (request.asdf) => serve subDocs 113 | 114 | return potentialDocs; 115 | } 116 | 117 | return []; 118 | }; 119 | 120 | const isScope = (x: string): x is Scope => { 121 | return (scopes as readonly string[]).includes(x); 122 | }; 123 | -------------------------------------------------------------------------------- /src/test/suite/Utils/Utils.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { describe } from 'mocha'; 3 | 4 | import * as vscode from 'vscode'; 5 | import { getWholeToken, getTokenUntil } from '../../../utils'; 6 | import { getDocForToken, getPotentialDocForPartial } from '../../../Documentation'; 7 | 8 | describe('utils test', () => { 9 | 10 | describe('getWholeToken', () => { 11 | 12 | test('should return a valid token when string is on position 0', async () => { 13 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 14 | const position = new vscode.Position(0, 8); 15 | 16 | assert.strictEqual(getWholeToken(document, position), 'one.two.three'); 17 | }); 18 | 19 | test('should return a valid token when string is further behind', async () => { 20 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 21 | const position = new vscode.Position(1, 8); 22 | 23 | assert.strictEqual(getWholeToken(document, position), 'one.two.three'); 24 | }); 25 | 26 | test('should avoid non-dot characters', async () => { 27 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 28 | const position = new vscode.Position(2, 5); 29 | 30 | assert.strictEqual(getWholeToken(document, position), 'two'); 31 | }); 32 | 33 | test('should get plain token between spaces', async () => { 34 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 35 | const position = new vscode.Position(3, 5); 36 | 37 | assert.strictEqual(getWholeToken(document, position), 'two'); 38 | }); 39 | 40 | test('should get dot-separated token between spaces', async () => { 41 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 42 | const position = new vscode.Position(4, 8); 43 | 44 | assert.strictEqual(getWholeToken(document, position), 'two.three'); 45 | }); 46 | 47 | }); 48 | 49 | describe('getTokenUntil', () => { 50 | 51 | test('should return a valid token when string is on position 0', async () => { 52 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 53 | const position = new vscode.Position(0, 8); 54 | 55 | assert.strictEqual(getTokenUntil(document, position), 'one.two.three'); 56 | }); 57 | 58 | test('should return a valid token when string is further behind', async () => { 59 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 60 | const position = new vscode.Position(1, 8); 61 | 62 | assert.strictEqual(getTokenUntil(document, position), 'one.two'); 63 | }); 64 | 65 | test('should avoid non-dot characters', async () => { 66 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 67 | const position = new vscode.Position(2, 5); 68 | 69 | assert.strictEqual(getTokenUntil(document, position), 'two'); 70 | }); 71 | 72 | test('should get plain token between spaces', async () => { 73 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 74 | const position = new vscode.Position(3, 5); 75 | 76 | assert.strictEqual(getTokenUntil(document, position), 'two'); 77 | }); 78 | 79 | test('should get dot-separated token between spaces', async () => { 80 | const document = await vscode.workspace.openTextDocument(__dirname + '/../../../../src/test/suite/Utils/example.test.rules'); 81 | const position = new vscode.Position(4, 8); 82 | 83 | assert.strictEqual(getTokenUntil(document, position), 'two.three'); 84 | }); 85 | 86 | }); 87 | 88 | describe('getDocForToken', () => { 89 | 90 | test('should return a valid documentation for a word in a valid token', async () => { 91 | const token = 'request.resource.data'; 92 | const markedWord = 'data'; 93 | 94 | assert.notStrictEqual(getDocForToken(token, markedWord), ''); 95 | }); 96 | 97 | test('should return no documentation for an invalid input', async () => { 98 | const token = 'request.resource.datafg'; 99 | const markedWord = 'gota'; 100 | 101 | assert.strictEqual(getDocForToken(token, markedWord), ''); 102 | }); 103 | 104 | test('should return valid documentation for exact token', async () => { 105 | const token = 'request.resource.data'; 106 | const markedWord = 'gota'; 107 | 108 | assert.notStrictEqual(getDocForToken(token, markedWord), ''); 109 | }); 110 | 111 | test('should lucky guess valid documentation for wrong token but exact marked word', async () => { 112 | const token = 'reqdfsuest.resodfurcedf.datsfa'; 113 | const markedWord = 'data'; 114 | 115 | assert.notStrictEqual(getDocForToken(token, markedWord), ''); 116 | }); 117 | 118 | test('should return different documentation for a word in different tokens', async () => { 119 | const tokenA = 'request.path'; 120 | const tokenB = 'path'; 121 | const markedWord = 'path'; 122 | 123 | assert.notStrictEqual(getDocForToken(tokenA, markedWord), getDocForToken(tokenB, markedWord)); 124 | }); 125 | 126 | }); 127 | 128 | describe('getPotentialDocForPartial', () => { 129 | 130 | test('should return a valid documentation for a valid partial', async () => { 131 | const partial = 'pat'; 132 | 133 | const potentialDocs = getPotentialDocForPartial(partial); 134 | 135 | if(potentialDocs.length > 0) { 136 | assert.ok(potentialDocs); 137 | } else { 138 | assert.fail('Expected potential docs to be found'); 139 | } 140 | }); 141 | 142 | test('should return no valid documentation for a invalid partial', async () => { 143 | const partial = 'asdf'; 144 | 145 | const potentialDocs = getPotentialDocForPartial(partial); 146 | 147 | if(potentialDocs.length > 0) { 148 | assert.fail(potentialDocs, [], 'Expected no potential docs to be found'); 149 | } else { 150 | assert.ok(potentialDocs); 151 | } 152 | }); 153 | 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /syntaxes/firestorerules.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "firestorerules", 4 | "scopeName": "source.firebase", 5 | "patterns": [ 6 | { 7 | "include": "#firestore-root" 8 | }, 9 | { 10 | "include": "#version" 11 | }, 12 | { 13 | "include": "#function-declaration" 14 | }, 15 | { 16 | "include": "#comments" 17 | } 18 | ], 19 | "repository": { 20 | "constants": { 21 | "patterns": [ 22 | { 23 | "name": "constant.numeric.firestorerules", 24 | "match": "\\d+" 25 | }, 26 | { 27 | "name": "constant.language.firestorerules", 28 | "match": "\\b(true|false|write|read|get|list|update|delete|create)\\b" 29 | } 30 | ] 31 | }, 32 | "comments": { 33 | "match": "\/\/.*", 34 | "name": "comment.line" 35 | }, 36 | "type-primitive": { 37 | "name": "support.type.primitive.fs", 38 | "match": "\\b(bool|bytes|constraint|duration|float|int|latlng|list|number|map|string|timestamp|path|request)\\b" 39 | }, 40 | "type-builtin-literals": { 41 | "name": "support.type.builtin.fs", 42 | "match": "\\b(true|false|null|undefined)\\b" 43 | }, 44 | "version": { 45 | "name": "comment.block.documentation.fs", 46 | "match": "rules_version?\\s*=?\\s*'\\d'?\\s*;" 47 | }, 48 | "firestore-root": { 49 | "name": "meta.root.fs", 50 | "begin": "(service)\\s+(cloud|firebase)\\.(firestore|storage)\\s*{", 51 | "beginCaptures": { 52 | "1": { 53 | "name": "keyword.control.fs" 54 | }, 55 | "2": { 56 | "name": "keyword.control.fs" 57 | }, 58 | "3": { 59 | "name": "storage.type.class.fs" 60 | } 61 | }, 62 | "end": "\\}", 63 | "patterns": [ 64 | { 65 | "include": "#matcher" 66 | }, 67 | { 68 | "include": "#function-declaration" 69 | }, 70 | { 71 | "include": "#comments" 72 | } 73 | ] 74 | }, 75 | "matcher": { 76 | "name": "meta.matcher.fs", 77 | "begin": "match", 78 | "beginCaptures": { 79 | "0": { 80 | "name": "keyword.control.fs" 81 | } 82 | }, 83 | "end": "\\}", 84 | "patterns": [ 85 | { 86 | "include": "#matcher" 87 | }, 88 | { 89 | "include": "#matcher-path" 90 | }, 91 | { 92 | "include": "#function-declaration" 93 | }, 94 | { 95 | "include": "#allow-statement" 96 | }, 97 | { 98 | "include": "#comments" 99 | } 100 | ] 101 | }, 102 | "matcher-path": { 103 | "name": "string.unquoted.fs", 104 | "begin": "(?<=match) \\/", 105 | "end": " ", 106 | "patterns": [ 107 | { 108 | "include": "#matcher-param" 109 | } 110 | ] 111 | }, 112 | "matcher-param": { 113 | "name": "variable.parameter.fs", 114 | "begin": "\\{\\w+", 115 | "end": "\\}" 116 | }, 117 | "function-declaration": { 118 | "name": "meta.function.fs", 119 | "begin": "(function)\\s+(\\w+)\\s?(\\()([^)]*)(\\))\\s*\\{", 120 | "beginCaptures": { 121 | "1": { 122 | "name": "storage.type.function.fs" 123 | }, 124 | "2": { 125 | "name": "support.function.fs" 126 | }, 127 | "3": { 128 | "name": "punctuation.definition.parameters.fs" 129 | }, 130 | "4": { 131 | "name": "variable.parameter.function.fs" 132 | }, 133 | "5": { 134 | "name": "punctuation.definition.parameters.fs" 135 | } 136 | }, 137 | "end": "(?=|<|>|!)" 279 | }, 280 | "strings": { 281 | "patterns": [ 282 | { 283 | "include": "#strings-quoted" 284 | }, 285 | { 286 | "name": "string.unquoted.firestorerules", 287 | "match": "\\/\\w((\\w|\\/)*)(?=\\W)", 288 | "patterns": [ 289 | { 290 | "name": "constant.character.escape.firestorerules", 291 | "match": "\\\\." 292 | } 293 | ] 294 | } 295 | ] 296 | }, 297 | "strings-quoted": { 298 | "name": "string.quoted.firestorerules", 299 | "begin": "\\\"|'", 300 | "end": "\\\"|'", 301 | "patterns": [ 302 | { 303 | "name": "constant.character.escape.firestorerules", 304 | "match": "\\\\." 305 | } 306 | ] 307 | }, 308 | "expression": { 309 | "patterns": [ 310 | { 311 | "include": "#paren-expression" 312 | }, 313 | { 314 | "include": "#operators" 315 | }, 316 | { 317 | "include": "#type-primitive" 318 | }, 319 | { 320 | "include": "#type-builtin-literals" 321 | }, 322 | { 323 | "include": "#function-call" 324 | }, 325 | { 326 | "include": "#identifiers" 327 | }, 328 | { 329 | "include": "#strings" 330 | }, 331 | { 332 | "include": "#comments" 333 | } 334 | ] 335 | }, 336 | "paren-expression": { 337 | "paren-expression": { 338 | "begin": "\\(", 339 | "beginCaptures": { 340 | "0": { 341 | "name": "meta.brace.round.fs" 342 | } 343 | }, 344 | "end": "\\)", 345 | "endCaptures": { 346 | "0": { 347 | "name": "meta.brace.round.fs" 348 | } 349 | }, 350 | "patterns": [ 351 | { 352 | "include": "#expression" 353 | } 354 | ] 355 | } 356 | }, 357 | "identifiers": { 358 | "patterns": [ 359 | { 360 | "name": "variable.parameter.fs", 361 | "match": "([_$[:alpha:]][_$[:alnum:]]*)" 362 | } 363 | ] 364 | } 365 | } 366 | } -------------------------------------------------------------------------------- /src/documentation/typeDocumentation.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, MarkdownString } from "vscode"; 2 | import { Documentation } from "./types"; 3 | 4 | // Mostly extracted from https://firebase.google.com/docs/reference/rules/index-all 5 | export const typeDoc: Readonly = { 6 | duration: { 7 | doc: 'Duration with nanosecond accuracy.', 8 | kind: CompletionItemKind.Class, 9 | childs: { 10 | nanos: { 11 | kind: CompletionItemKind.Method, 12 | header: 'nanos() returns rules.Integer', 13 | doc: 'Get the nanoseconds portion (signed) of the duration from -999,999,999 to +999,999,999 inclusive.' 14 | }, 15 | seconds: { 16 | kind: CompletionItemKind.Method, 17 | header: 'seconds() returns rules.Integer', 18 | doc: 'Get the seconds portion (signed) of the duration from -315,576,000,000 to +315,576,000,000 inclusive.' 19 | } 20 | } 21 | }, 22 | latlng: { 23 | doc: 'Type representing a geopoint.', 24 | kind: CompletionItemKind.Class, 25 | childs: { 26 | distance: { 27 | kind: CompletionItemKind.Method, 28 | doc: 'Calculate distance between two LatLng points in distance (meters).', 29 | }, 30 | latitude: { 31 | kind: CompletionItemKind.Method, 32 | doc: 'Get the latitude value in the range [-90.0, 90.0].' 33 | }, 34 | longitude: { 35 | kind: CompletionItemKind.Method, 36 | doc: 'Get the longitude value in the range [-180.0, 180.0].' 37 | } 38 | } 39 | }, 40 | list: { 41 | doc: new MarkdownString( 42 | `List type. 43 | Items are not necessarily homogenous.`), 44 | kind: CompletionItemKind.Class, 45 | childs: { 46 | hasAll: { 47 | kind: CompletionItemKind.Method, 48 | doc: 'Determine whether the list contains all elements in another list.', 49 | }, 50 | hasAny: { 51 | kind: CompletionItemKind.Method, 52 | doc: 'Determine whether the list contains any element in another list.' 53 | }, 54 | hasOnly: { 55 | kind: CompletionItemKind.Method, 56 | doc: 'Determine whether all elements in the list are present in another list.' 57 | }, 58 | join: { 59 | kind: CompletionItemKind.Method, 60 | doc: 'Join the elements in the list into a string, with a separator.' 61 | }, 62 | size: { 63 | kind: CompletionItemKind.Method, 64 | doc: 'Get the number of values in the list.' 65 | } 66 | } 67 | }, 68 | map: { 69 | doc: 'Map type, used for simple key-value mappings.', 70 | kind: CompletionItemKind.Class, 71 | childs: { 72 | keys: { 73 | kind: CompletionItemKind.Method, 74 | doc: 'Get the list of keys in the map.', 75 | }, 76 | values: { 77 | kind: CompletionItemKind.Method, 78 | doc: 'Get the list of values in the map.' 79 | } 80 | } 81 | }, 82 | number: { 83 | doc: 'A value of type Integer or type Float', 84 | kind: CompletionItemKind.Class, 85 | }, 86 | path: { 87 | doc: new MarkdownString('path[type]: \nDirectory-like pattern for the location of a resource. Paths can be created in two ways. The first is in the "raw" form beginning with a forward slash `/`. The second is by converting from a string using the path() function.'), 88 | kind: CompletionItemKind.Class, 89 | childs: { 90 | bind: { 91 | kind: CompletionItemKind.Method, 92 | doc: 'Bind key-value pairs in a map to a path.', 93 | } 94 | } 95 | }, 96 | request: { 97 | doc: 'The incoming request context', 98 | kind: CompletionItemKind.Class, 99 | childs: { 100 | resource: { 101 | doc: new MarkdownString(`The new resource value, present on write requests only. It contains the following information: 102 | * \`data\` - a Map of the document data. 103 | * \`id\` - a String of the document's key.`), 104 | kind: CompletionItemKind.Field, 105 | childs: { 106 | data: { 107 | doc: 'A map of the document\'s data.', 108 | kind: CompletionItemKind.Field 109 | }, 110 | id: { 111 | doc: 'A string of the document\'s key.', 112 | kind: CompletionItemKind.Field 113 | } 114 | } 115 | }, 116 | auth: { 117 | doc: new MarkdownString(`Request authentication context. It contains the following information: 118 | * \`uid\` - the UID of the requesting user. 119 | * \`token\` - a map of JWT token claims.`), 120 | kind: CompletionItemKind.Field, 121 | childs: { 122 | uid: { 123 | doc: 'The UID of the requesting user.', 124 | kind: CompletionItemKind.Field 125 | }, 126 | token: { 127 | doc: 'A map of JWT token claims.', 128 | kind: CompletionItemKind.Field, 129 | childs: { 130 | email: { 131 | doc: 'The email address associated with the account, if present.', 132 | kind: CompletionItemKind.Field, 133 | }, 134 | email_verified: { 135 | doc: 'true if the user has verified they have access to the email address. Some providers automatically verify email addresses they own.', 136 | kind: CompletionItemKind.Field, 137 | }, 138 | phone_number: { 139 | doc: 'The phone number associated with the account, if present.', 140 | kind: CompletionItemKind.Field, 141 | }, 142 | name: { 143 | doc: 'The user\'s display name, if set.', 144 | kind: CompletionItemKind.Field, 145 | }, 146 | sub: { 147 | doc: 'The user\'s Firebase UID. This is unique within a project.', 148 | kind: CompletionItemKind.Field, 149 | }, 150 | firebase: { 151 | doc: 'Firebase data.', 152 | kind: CompletionItemKind.Field, 153 | childs: { 154 | identities: { 155 | doc: `Dictionary of all the identities that are associated with this user's account. 156 | The keys of the dictionary can be any of the following: email, phone, google.com, facebook.com, github.com, twitter.com. 157 | The values of the dictionary are arrays of unique identifiers for each identity provider associated with the account. 158 | For example, auth.token.firebase.identities["google.com"][0] contains the first Google user ID associated with the account.`, 159 | kind: CompletionItemKind.Field 160 | }, 161 | sign_in_provider: { 162 | doc: `The sign-in provider used to obtain this token. 163 | Can be one of the following strings: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.`, 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | }, 171 | method: { 172 | doc: new MarkdownString('The request method. One of \n* get\n* list\n* create\n* update\n* delete'), 173 | kind: CompletionItemKind.EnumMember 174 | }, 175 | path: { 176 | doc: 'Path of the affected resource.', 177 | kind: CompletionItemKind.Field 178 | }, 179 | query: { 180 | doc: new MarkdownString(`Map of query properties, when present. 181 | * limit - query limit clause. 182 | * offset - query offset clause. 183 | * orderBy - query orderBy clause.`), 184 | kind: CompletionItemKind.Field 185 | }, 186 | time: { 187 | doc: new MarkdownString(`When the request was received by the service. 188 | For Firestore write operations that include server-side timestamps, this time will be equal to the server timestamp.`), 189 | kind: CompletionItemKind.Field 190 | } 191 | } 192 | }, 193 | resource: { 194 | doc: 'The firestore document being read or written.', 195 | kind: CompletionItemKind.Class, 196 | childs: { 197 | __name__: { 198 | doc: 'The full document name, as a path.', 199 | kind: CompletionItemKind.Field 200 | }, 201 | data: { 202 | doc: 'Map of the document data.', 203 | kind: CompletionItemKind.Field 204 | }, 205 | id: { 206 | doc: 'String of the document\'s key.', 207 | kind: CompletionItemKind.Field 208 | } 209 | } 210 | }, 211 | string: { 212 | doc: new MarkdownString( 213 | `Primitive type representing a string value. 214 | Strings can be lexicographically compared using the ==, !=, >, <, >= and <= operators. 215 | Strings can be concatenated using the + operator. 216 | Sub-strings can be accessed using the index operator [] and the range operator [i:j]. 217 | Boolean, integer, float, and null values can be converted into strings using the string() function. `), 218 | kind: CompletionItemKind.Class, 219 | childs: { 220 | lower: { 221 | header: 'lower() returns rules.String', 222 | doc: 'Returns a lowercase version of the input string.', 223 | kind: CompletionItemKind.Method 224 | }, 225 | matches: { 226 | header: 'matches(re) returns rules.Boolean', 227 | doc: 'Performs a regular expression match on the whole string.', 228 | kind: CompletionItemKind.Method 229 | }, 230 | split: { 231 | header: 'split(re) returns rules.List', 232 | doc: 'Splits a string according to a regular expression.', 233 | kind: CompletionItemKind.Method 234 | }, 235 | trim: { 236 | header: 'trim() returns rules.String', 237 | doc: 'Returns a version of the string with leading and trailing spaces removed.', 238 | kind: CompletionItemKind.Method 239 | }, 240 | upper: { 241 | header: 'upper() returns rules.String', 242 | doc: 'Returns an uppercase version of the input string.', 243 | kind: CompletionItemKind.Method 244 | }, 245 | } 246 | }, 247 | timestamp: { 248 | doc: 'A timestamp in UTC with nanosecond accuracy', 249 | kind: CompletionItemKind.Class, 250 | childs: { 251 | date: { 252 | header: 'date() returns rules.Timestamp', 253 | doc: 'Timestamp value containing year, month, and day only.', 254 | kind: CompletionItemKind.Method 255 | }, 256 | day: { 257 | header: 'day() returns rules.Integer', 258 | doc: 'Get the day value of the timestamp.', 259 | kind: CompletionItemKind.Method 260 | }, 261 | dayOfWeek: { 262 | header: 'dayOfWeek() returns rules.Integer', 263 | doc: 'Get the day of the week as a value from 1 to 7.', 264 | kind: CompletionItemKind.Method 265 | }, 266 | dayOfYear: { 267 | header: 'dayOfYear() returns rules.Integer', 268 | doc: 'Get the day of the year as a value from 1 to 366.', 269 | kind: CompletionItemKind.Method 270 | }, 271 | hours: { 272 | header: 'hours() returns rules.Integer', 273 | doc: 'Get the hours value of the timestamp.', 274 | kind: CompletionItemKind.Method 275 | }, 276 | minutes: { 277 | header: 'minutes() returns rules.Integer', 278 | doc: 'Get the minutes value of the timestamp.', 279 | kind: CompletionItemKind.Method 280 | }, 281 | seconds: { 282 | header: 'seconds() returns rules.Integer', 283 | doc: 'Get the seconds value of the timestamp.', 284 | kind: CompletionItemKind.Method 285 | }, 286 | nanos: { 287 | header: 'nanos() returns rules.Integer', 288 | doc: 'Get the nanos value of the timestamp.', 289 | kind: CompletionItemKind.Method 290 | }, 291 | month: { 292 | header: 'month() returns rules.Integer', 293 | doc: 'Get the month value of the timestamp.', 294 | kind: CompletionItemKind.Method 295 | }, 296 | toMillis: { 297 | header: 'toMillis() returns rules.Integer', 298 | doc: 'Get the time in milliseconds since the epoch.', 299 | kind: CompletionItemKind.Method 300 | }, 301 | year: { 302 | header: 'year() returns rules.Integer', 303 | doc: 'Get the year value of the timestamp', 304 | kind: CompletionItemKind.Method 305 | }, 306 | time: { 307 | header: 'time() returns rules.Duration', 308 | doc: 'Get the duration value from the time portion of the timestamp', 309 | kind: CompletionItemKind.Method 310 | } 311 | } 312 | } 313 | }; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.0.0": 6 | version "7.10.4" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" 8 | integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== 9 | dependencies: 10 | "@babel/highlight" "^7.10.4" 11 | 12 | "@babel/helper-validator-identifier@^7.10.4": 13 | version "7.10.4" 14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" 15 | integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== 16 | 17 | "@babel/highlight@^7.10.4": 18 | version "7.10.4" 19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" 20 | integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== 21 | dependencies: 22 | "@babel/helper-validator-identifier" "^7.10.4" 23 | chalk "^2.0.0" 24 | js-tokens "^4.0.0" 25 | 26 | "@eslint/eslintrc@^0.1.3": 27 | version "0.1.3" 28 | resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" 29 | integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== 30 | dependencies: 31 | ajv "^6.12.4" 32 | debug "^4.1.1" 33 | espree "^7.3.0" 34 | globals "^12.1.0" 35 | ignore "^4.0.6" 36 | import-fresh "^3.2.1" 37 | js-yaml "^3.13.1" 38 | lodash "^4.17.19" 39 | minimatch "^3.0.4" 40 | strip-json-comments "^3.1.1" 41 | 42 | "@types/array.prototype.flatmap@^1.2.1": 43 | version "1.2.2" 44 | resolved "https://registry.yarnpkg.com/@types/array.prototype.flatmap/-/array.prototype.flatmap-1.2.2.tgz#9041c2dc907d583ffb80b8882a782b42436d57c1" 45 | integrity sha512-dto5M/8GxPzjaScvQeft2IG0EkoZZfPg2+1noM2BWiU1VR2zsGHf76LonTOnLQKDuJlKDLzKaru4b+5Sci0Yhg== 46 | 47 | "@types/chai@^4.2.11": 48 | version "4.2.12" 49 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.12.tgz#6160ae454cd89dae05adc3bb97997f488b608201" 50 | integrity sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ== 51 | 52 | "@types/color-name@^1.1.1": 53 | version "1.1.1" 54 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" 55 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== 56 | 57 | "@types/eslint-visitor-keys@^1.0.0": 58 | version "1.0.0" 59 | resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" 60 | integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== 61 | 62 | "@types/glob@^7.1.1": 63 | version "7.1.3" 64 | resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" 65 | integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== 66 | dependencies: 67 | "@types/minimatch" "*" 68 | "@types/node" "*" 69 | 70 | "@types/json-schema@^7.0.3": 71 | version "7.0.6" 72 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" 73 | integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== 74 | 75 | "@types/minimatch@*": 76 | version "3.0.3" 77 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" 78 | integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== 79 | 80 | "@types/mocha@^7.0.2": 81 | version "7.0.2" 82 | resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" 83 | integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== 84 | 85 | "@types/node@*": 86 | version "14.10.2" 87 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.2.tgz#9b47a2c8e4dabd4db73b57e750b24af689600514" 88 | integrity sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw== 89 | 90 | "@types/node@^13.5.0": 91 | version "13.13.20" 92 | resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.20.tgz#8196a4db574220fc50e2e54f250ad51179bd0a03" 93 | integrity sha512-1kx55tU3AvGX2Cjk2W4GMBxbgIz892V+X10S2gUreIAq8qCWgaQH+tZBOWc0bi2BKFhQt+CX0BTx28V9QPNa+A== 94 | 95 | "@types/oniguruma@^7.0.1": 96 | version "7.0.1" 97 | resolved "https://registry.yarnpkg.com/@types/oniguruma/-/oniguruma-7.0.1.tgz#7aaf26127c97d239d24480d931e5b71dc0ddeded" 98 | integrity sha512-1MxY/ooNO67EQv7Jlv9v/lG1Cll26Uqo4mY00dNPE6TZ62sTJ39WTlEOWbLwn7elRpmqT6hX3fUaBhFeFcQeuA== 99 | 100 | "@types/prettier@^2.1.1": 101 | version "2.1.1" 102 | resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.1.tgz#be148756d5480a84cde100324c03a86ae5739fb5" 103 | integrity sha512-2zs+O+UkDsJ1Vcp667pd3f8xearMdopz/z54i99wtRDI5KLmngk7vlrYZD0ZjKHaROR03EznlBbVY9PfAEyJIQ== 104 | 105 | "@types/vscode@^1.2.0": 106 | version "1.49.0" 107 | resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.49.0.tgz#f3731d97d7e8b2697510eb26f6e6d04ee8c17352" 108 | integrity sha512-wfNQmLmm1VdMBr6iuNdprWmC1YdrgZ9dQzadv+l2eSjJlElOdJw8OTm4RU4oGTBcfvG6RZI2jOcppkdSS18mZw== 109 | 110 | "@typescript-eslint/eslint-plugin@^3.4.0": 111 | version "3.10.1" 112 | resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" 113 | integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== 114 | dependencies: 115 | "@typescript-eslint/experimental-utils" "3.10.1" 116 | debug "^4.1.1" 117 | functional-red-black-tree "^1.0.1" 118 | regexpp "^3.0.0" 119 | semver "^7.3.2" 120 | tsutils "^3.17.1" 121 | 122 | "@typescript-eslint/experimental-utils@3.10.1": 123 | version "3.10.1" 124 | resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" 125 | integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== 126 | dependencies: 127 | "@types/json-schema" "^7.0.3" 128 | "@typescript-eslint/types" "3.10.1" 129 | "@typescript-eslint/typescript-estree" "3.10.1" 130 | eslint-scope "^5.0.0" 131 | eslint-utils "^2.0.0" 132 | 133 | "@typescript-eslint/parser@^3.4.0": 134 | version "3.10.1" 135 | resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" 136 | integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== 137 | dependencies: 138 | "@types/eslint-visitor-keys" "^1.0.0" 139 | "@typescript-eslint/experimental-utils" "3.10.1" 140 | "@typescript-eslint/types" "3.10.1" 141 | "@typescript-eslint/typescript-estree" "3.10.1" 142 | eslint-visitor-keys "^1.1.0" 143 | 144 | "@typescript-eslint/types@3.10.1": 145 | version "3.10.1" 146 | resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" 147 | integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== 148 | 149 | "@typescript-eslint/typescript-estree@3.10.1": 150 | version "3.10.1" 151 | resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" 152 | integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== 153 | dependencies: 154 | "@typescript-eslint/types" "3.10.1" 155 | "@typescript-eslint/visitor-keys" "3.10.1" 156 | debug "^4.1.1" 157 | glob "^7.1.6" 158 | is-glob "^4.0.1" 159 | lodash "^4.17.15" 160 | semver "^7.3.2" 161 | tsutils "^3.17.1" 162 | 163 | "@typescript-eslint/visitor-keys@3.10.1": 164 | version "3.10.1" 165 | resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" 166 | integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== 167 | dependencies: 168 | eslint-visitor-keys "^1.1.0" 169 | 170 | acorn-jsx@^5.2.0: 171 | version "5.3.1" 172 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" 173 | integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== 174 | 175 | acorn@^7.4.0: 176 | version "7.4.0" 177 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" 178 | integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== 179 | 180 | agent-base@4, agent-base@^4.3.0: 181 | version "4.3.0" 182 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" 183 | integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== 184 | dependencies: 185 | es6-promisify "^5.0.0" 186 | 187 | ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: 188 | version "6.12.5" 189 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" 190 | integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== 191 | dependencies: 192 | fast-deep-equal "^3.1.1" 193 | fast-json-stable-stringify "^2.0.0" 194 | json-schema-traverse "^0.4.1" 195 | uri-js "^4.2.2" 196 | 197 | ansi-colors@3.2.3: 198 | version "3.2.3" 199 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" 200 | integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== 201 | 202 | ansi-colors@^4.1.1: 203 | version "4.1.1" 204 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 205 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 206 | 207 | ansi-regex@^3.0.0: 208 | version "3.0.1" 209 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" 210 | integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== 211 | 212 | ansi-regex@^4.1.0: 213 | version "4.1.0" 214 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 215 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 216 | 217 | ansi-regex@^5.0.0: 218 | version "5.0.0" 219 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 220 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 221 | 222 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: 223 | version "3.2.1" 224 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 225 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 226 | dependencies: 227 | color-convert "^1.9.0" 228 | 229 | ansi-styles@^4.1.0: 230 | version "4.2.1" 231 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" 232 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== 233 | dependencies: 234 | "@types/color-name" "^1.1.1" 235 | color-convert "^2.0.1" 236 | 237 | anymatch@~3.1.1: 238 | version "3.1.1" 239 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" 240 | integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== 241 | dependencies: 242 | normalize-path "^3.0.0" 243 | picomatch "^2.0.4" 244 | 245 | argparse@^1.0.7: 246 | version "1.0.10" 247 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 248 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 249 | dependencies: 250 | sprintf-js "~1.0.2" 251 | 252 | array.prototype.flatmap@^1.2.3: 253 | version "1.2.3" 254 | resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" 255 | integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== 256 | dependencies: 257 | define-properties "^1.1.3" 258 | es-abstract "^1.17.0-next.1" 259 | function-bind "^1.1.1" 260 | 261 | assertion-error@^1.1.0: 262 | version "1.1.0" 263 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 264 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 265 | 266 | astral-regex@^1.0.0: 267 | version "1.0.0" 268 | resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" 269 | integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== 270 | 271 | balanced-match@^1.0.0: 272 | version "1.0.0" 273 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 274 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 275 | 276 | binary-extensions@^2.0.0: 277 | version "2.1.0" 278 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" 279 | integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== 280 | 281 | brace-expansion@^1.1.7: 282 | version "1.1.11" 283 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 284 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 285 | dependencies: 286 | balanced-match "^1.0.0" 287 | concat-map "0.0.1" 288 | 289 | braces@~3.0.2: 290 | version "3.0.2" 291 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 292 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 293 | dependencies: 294 | fill-range "^7.0.1" 295 | 296 | browser-stdout@1.3.1: 297 | version "1.3.1" 298 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 299 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 300 | 301 | callsites@^3.0.0: 302 | version "3.1.0" 303 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 304 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 305 | 306 | camelcase@^5.0.0: 307 | version "5.3.1" 308 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 309 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 310 | 311 | chai@^4.2.0: 312 | version "4.2.0" 313 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" 314 | integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== 315 | dependencies: 316 | assertion-error "^1.1.0" 317 | check-error "^1.0.2" 318 | deep-eql "^3.0.1" 319 | get-func-name "^2.0.0" 320 | pathval "^1.1.0" 321 | type-detect "^4.0.5" 322 | 323 | chalk@^2.0.0, chalk@^2.4.2: 324 | version "2.4.2" 325 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 326 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 327 | dependencies: 328 | ansi-styles "^3.2.1" 329 | escape-string-regexp "^1.0.5" 330 | supports-color "^5.3.0" 331 | 332 | chalk@^4.0.0: 333 | version "4.1.0" 334 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" 335 | integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== 336 | dependencies: 337 | ansi-styles "^4.1.0" 338 | supports-color "^7.1.0" 339 | 340 | check-error@^1.0.2: 341 | version "1.0.2" 342 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 343 | integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 344 | 345 | chokidar@3.3.0: 346 | version "3.3.0" 347 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" 348 | integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== 349 | dependencies: 350 | anymatch "~3.1.1" 351 | braces "~3.0.2" 352 | glob-parent "~5.1.0" 353 | is-binary-path "~2.1.0" 354 | is-glob "~4.0.1" 355 | normalize-path "~3.0.0" 356 | readdirp "~3.2.0" 357 | optionalDependencies: 358 | fsevents "~2.1.1" 359 | 360 | cliui@^5.0.0: 361 | version "5.0.0" 362 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 363 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== 364 | dependencies: 365 | string-width "^3.1.0" 366 | strip-ansi "^5.2.0" 367 | wrap-ansi "^5.1.0" 368 | 369 | color-convert@^1.9.0: 370 | version "1.9.3" 371 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 372 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 373 | dependencies: 374 | color-name "1.1.3" 375 | 376 | color-convert@^2.0.1: 377 | version "2.0.1" 378 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 379 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 380 | dependencies: 381 | color-name "~1.1.4" 382 | 383 | color-name@1.1.3: 384 | version "1.1.3" 385 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 386 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 387 | 388 | color-name@~1.1.4: 389 | version "1.1.4" 390 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 391 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 392 | 393 | concat-map@0.0.1: 394 | version "0.0.1" 395 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 396 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 397 | 398 | cross-spawn@^7.0.2: 399 | version "7.0.3" 400 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 401 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 402 | dependencies: 403 | path-key "^3.1.0" 404 | shebang-command "^2.0.0" 405 | which "^2.0.1" 406 | 407 | debug@3.1.0: 408 | version "3.1.0" 409 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 410 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 411 | dependencies: 412 | ms "2.0.0" 413 | 414 | debug@3.2.6, debug@^3.1.0: 415 | version "3.2.6" 416 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 417 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 418 | dependencies: 419 | ms "^2.1.1" 420 | 421 | debug@^4.0.1, debug@^4.1.1: 422 | version "4.1.1" 423 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 424 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 425 | dependencies: 426 | ms "^2.1.1" 427 | 428 | decamelize@^1.2.0: 429 | version "1.2.0" 430 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 431 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 432 | 433 | deep-eql@^3.0.1: 434 | version "3.0.1" 435 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 436 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 437 | dependencies: 438 | type-detect "^4.0.0" 439 | 440 | deep-is@^0.1.3: 441 | version "0.1.3" 442 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 443 | integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= 444 | 445 | define-properties@^1.1.2, define-properties@^1.1.3: 446 | version "1.1.3" 447 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 448 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 449 | dependencies: 450 | object-keys "^1.0.12" 451 | 452 | diff@3.5.0: 453 | version "3.5.0" 454 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 455 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 456 | 457 | doctrine@^3.0.0: 458 | version "3.0.0" 459 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 460 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 461 | dependencies: 462 | esutils "^2.0.2" 463 | 464 | emoji-regex@^7.0.1: 465 | version "7.0.3" 466 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 467 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 468 | 469 | enquirer@^2.3.5: 470 | version "2.3.6" 471 | resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" 472 | integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== 473 | dependencies: 474 | ansi-colors "^4.1.1" 475 | 476 | es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: 477 | version "1.17.6" 478 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" 479 | integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== 480 | dependencies: 481 | es-to-primitive "^1.2.1" 482 | function-bind "^1.1.1" 483 | has "^1.0.3" 484 | has-symbols "^1.0.1" 485 | is-callable "^1.2.0" 486 | is-regex "^1.1.0" 487 | object-inspect "^1.7.0" 488 | object-keys "^1.1.1" 489 | object.assign "^4.1.0" 490 | string.prototype.trimend "^1.0.1" 491 | string.prototype.trimstart "^1.0.1" 492 | 493 | es-abstract@^1.18.0-next.0: 494 | version "1.18.0-next.0" 495 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" 496 | integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== 497 | dependencies: 498 | es-to-primitive "^1.2.1" 499 | function-bind "^1.1.1" 500 | has "^1.0.3" 501 | has-symbols "^1.0.1" 502 | is-callable "^1.2.0" 503 | is-negative-zero "^2.0.0" 504 | is-regex "^1.1.1" 505 | object-inspect "^1.8.0" 506 | object-keys "^1.1.1" 507 | object.assign "^4.1.0" 508 | string.prototype.trimend "^1.0.1" 509 | string.prototype.trimstart "^1.0.1" 510 | 511 | es-to-primitive@^1.2.1: 512 | version "1.2.1" 513 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" 514 | integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== 515 | dependencies: 516 | is-callable "^1.1.4" 517 | is-date-object "^1.0.1" 518 | is-symbol "^1.0.2" 519 | 520 | es6-promise@^4.0.3: 521 | version "4.2.8" 522 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" 523 | integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== 524 | 525 | es6-promisify@^5.0.0: 526 | version "5.0.0" 527 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 528 | integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= 529 | dependencies: 530 | es6-promise "^4.0.3" 531 | 532 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: 533 | version "1.0.5" 534 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 535 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 536 | 537 | eslint-scope@^5.0.0, eslint-scope@^5.1.0: 538 | version "5.1.1" 539 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 540 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 541 | dependencies: 542 | esrecurse "^4.3.0" 543 | estraverse "^4.1.1" 544 | 545 | eslint-utils@^2.0.0, eslint-utils@^2.1.0: 546 | version "2.1.0" 547 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" 548 | integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== 549 | dependencies: 550 | eslint-visitor-keys "^1.1.0" 551 | 552 | eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: 553 | version "1.3.0" 554 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" 555 | integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== 556 | 557 | eslint@^7.3.1: 558 | version "7.9.0" 559 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337" 560 | integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA== 561 | dependencies: 562 | "@babel/code-frame" "^7.0.0" 563 | "@eslint/eslintrc" "^0.1.3" 564 | ajv "^6.10.0" 565 | chalk "^4.0.0" 566 | cross-spawn "^7.0.2" 567 | debug "^4.0.1" 568 | doctrine "^3.0.0" 569 | enquirer "^2.3.5" 570 | eslint-scope "^5.1.0" 571 | eslint-utils "^2.1.0" 572 | eslint-visitor-keys "^1.3.0" 573 | espree "^7.3.0" 574 | esquery "^1.2.0" 575 | esutils "^2.0.2" 576 | file-entry-cache "^5.0.1" 577 | functional-red-black-tree "^1.0.1" 578 | glob-parent "^5.0.0" 579 | globals "^12.1.0" 580 | ignore "^4.0.6" 581 | import-fresh "^3.0.0" 582 | imurmurhash "^0.1.4" 583 | is-glob "^4.0.0" 584 | js-yaml "^3.13.1" 585 | json-stable-stringify-without-jsonify "^1.0.1" 586 | levn "^0.4.1" 587 | lodash "^4.17.19" 588 | minimatch "^3.0.4" 589 | natural-compare "^1.4.0" 590 | optionator "^0.9.1" 591 | progress "^2.0.0" 592 | regexpp "^3.1.0" 593 | semver "^7.2.1" 594 | strip-ansi "^6.0.0" 595 | strip-json-comments "^3.1.0" 596 | table "^5.2.3" 597 | text-table "^0.2.0" 598 | v8-compile-cache "^2.0.3" 599 | 600 | espree@^7.3.0: 601 | version "7.3.0" 602 | resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" 603 | integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== 604 | dependencies: 605 | acorn "^7.4.0" 606 | acorn-jsx "^5.2.0" 607 | eslint-visitor-keys "^1.3.0" 608 | 609 | esprima@^4.0.0: 610 | version "4.0.1" 611 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 612 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 613 | 614 | esquery@^1.2.0: 615 | version "1.3.1" 616 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" 617 | integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== 618 | dependencies: 619 | estraverse "^5.1.0" 620 | 621 | esrecurse@^4.3.0: 622 | version "4.3.0" 623 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 624 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 625 | dependencies: 626 | estraverse "^5.2.0" 627 | 628 | estraverse@^4.1.1: 629 | version "4.3.0" 630 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 631 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 632 | 633 | estraverse@^5.1.0, estraverse@^5.2.0: 634 | version "5.2.0" 635 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" 636 | integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== 637 | 638 | esutils@^2.0.2: 639 | version "2.0.3" 640 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 641 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 642 | 643 | fast-deep-equal@^3.1.1: 644 | version "3.1.3" 645 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 646 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 647 | 648 | fast-json-stable-stringify@^2.0.0: 649 | version "2.1.0" 650 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 651 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 652 | 653 | fast-levenshtein@^2.0.6: 654 | version "2.0.6" 655 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 656 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 657 | 658 | file-entry-cache@^5.0.1: 659 | version "5.0.1" 660 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" 661 | integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== 662 | dependencies: 663 | flat-cache "^2.0.1" 664 | 665 | fill-range@^7.0.1: 666 | version "7.0.1" 667 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 668 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 669 | dependencies: 670 | to-regex-range "^5.0.1" 671 | 672 | find-up@3.0.0, find-up@^3.0.0: 673 | version "3.0.0" 674 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 675 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 676 | dependencies: 677 | locate-path "^3.0.0" 678 | 679 | flat-cache@^2.0.1: 680 | version "2.0.1" 681 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" 682 | integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== 683 | dependencies: 684 | flatted "^2.0.0" 685 | rimraf "2.6.3" 686 | write "1.0.3" 687 | 688 | flat@^4.1.0: 689 | version "4.1.0" 690 | resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" 691 | integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== 692 | dependencies: 693 | is-buffer "~2.0.3" 694 | 695 | flatted@^2.0.0: 696 | version "2.0.2" 697 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" 698 | integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== 699 | 700 | fs.realpath@^1.0.0: 701 | version "1.0.0" 702 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 703 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 704 | 705 | fsevents@~2.1.1: 706 | version "2.1.3" 707 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" 708 | integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== 709 | 710 | function-bind@^1.1.1: 711 | version "1.1.1" 712 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 713 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 714 | 715 | functional-red-black-tree@^1.0.1: 716 | version "1.0.1" 717 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 718 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 719 | 720 | get-caller-file@^2.0.1: 721 | version "2.0.5" 722 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 723 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 724 | 725 | get-func-name@^2.0.0: 726 | version "2.0.0" 727 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 728 | integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 729 | 730 | glob-parent@^5.0.0, glob-parent@~5.1.0: 731 | version "5.1.2" 732 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 733 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 734 | dependencies: 735 | is-glob "^4.0.1" 736 | 737 | glob@7.1.3: 738 | version "7.1.3" 739 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 740 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 741 | dependencies: 742 | fs.realpath "^1.0.0" 743 | inflight "^1.0.4" 744 | inherits "2" 745 | minimatch "^3.0.4" 746 | once "^1.3.0" 747 | path-is-absolute "^1.0.0" 748 | 749 | glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: 750 | version "7.1.6" 751 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 752 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 753 | dependencies: 754 | fs.realpath "^1.0.0" 755 | inflight "^1.0.4" 756 | inherits "2" 757 | minimatch "^3.0.4" 758 | once "^1.3.0" 759 | path-is-absolute "^1.0.0" 760 | 761 | globals@^12.1.0: 762 | version "12.4.0" 763 | resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" 764 | integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== 765 | dependencies: 766 | type-fest "^0.8.1" 767 | 768 | growl@1.10.5: 769 | version "1.10.5" 770 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 771 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 772 | 773 | has-flag@^3.0.0: 774 | version "3.0.0" 775 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 776 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 777 | 778 | has-flag@^4.0.0: 779 | version "4.0.0" 780 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 781 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 782 | 783 | has-symbols@^1.0.0, has-symbols@^1.0.1: 784 | version "1.0.1" 785 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" 786 | integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== 787 | 788 | has@^1.0.3: 789 | version "1.0.3" 790 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 791 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 792 | dependencies: 793 | function-bind "^1.1.1" 794 | 795 | he@1.2.0: 796 | version "1.2.0" 797 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 798 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 799 | 800 | http-proxy-agent@^2.1.0: 801 | version "2.1.0" 802 | resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" 803 | integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== 804 | dependencies: 805 | agent-base "4" 806 | debug "3.1.0" 807 | 808 | https-proxy-agent@^2.2.4: 809 | version "2.2.4" 810 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" 811 | integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== 812 | dependencies: 813 | agent-base "^4.3.0" 814 | debug "^3.1.0" 815 | 816 | ignore@^4.0.6: 817 | version "4.0.6" 818 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" 819 | integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== 820 | 821 | import-fresh@^3.0.0, import-fresh@^3.2.1: 822 | version "3.2.1" 823 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" 824 | integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== 825 | dependencies: 826 | parent-module "^1.0.0" 827 | resolve-from "^4.0.0" 828 | 829 | imurmurhash@^0.1.4: 830 | version "0.1.4" 831 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 832 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 833 | 834 | inflight@^1.0.4: 835 | version "1.0.6" 836 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 837 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 838 | dependencies: 839 | once "^1.3.0" 840 | wrappy "1" 841 | 842 | inherits@2: 843 | version "2.0.4" 844 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 845 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 846 | 847 | is-binary-path@~2.1.0: 848 | version "2.1.0" 849 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 850 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 851 | dependencies: 852 | binary-extensions "^2.0.0" 853 | 854 | is-buffer@~2.0.3: 855 | version "2.0.4" 856 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" 857 | integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== 858 | 859 | is-callable@^1.1.4, is-callable@^1.2.0: 860 | version "1.2.1" 861 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d" 862 | integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg== 863 | 864 | is-date-object@^1.0.1: 865 | version "1.0.2" 866 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" 867 | integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== 868 | 869 | is-extglob@^2.1.1: 870 | version "2.1.1" 871 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 872 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 873 | 874 | is-fullwidth-code-point@^2.0.0: 875 | version "2.0.0" 876 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 877 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 878 | 879 | is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: 880 | version "4.0.1" 881 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 882 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 883 | dependencies: 884 | is-extglob "^2.1.1" 885 | 886 | is-negative-zero@^2.0.0: 887 | version "2.0.0" 888 | resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" 889 | integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= 890 | 891 | is-number@^7.0.0: 892 | version "7.0.0" 893 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 894 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 895 | 896 | is-regex@^1.1.0, is-regex@^1.1.1: 897 | version "1.1.1" 898 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" 899 | integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== 900 | dependencies: 901 | has-symbols "^1.0.1" 902 | 903 | is-symbol@^1.0.2: 904 | version "1.0.3" 905 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" 906 | integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== 907 | dependencies: 908 | has-symbols "^1.0.1" 909 | 910 | isexe@^2.0.0: 911 | version "2.0.0" 912 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 913 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 914 | 915 | js-tokens@^4.0.0: 916 | version "4.0.0" 917 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 918 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 919 | 920 | js-yaml@3.13.1: 921 | version "3.13.1" 922 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 923 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 924 | dependencies: 925 | argparse "^1.0.7" 926 | esprima "^4.0.0" 927 | 928 | js-yaml@^3.13.1: 929 | version "3.14.0" 930 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" 931 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== 932 | dependencies: 933 | argparse "^1.0.7" 934 | esprima "^4.0.0" 935 | 936 | json-schema-traverse@^0.4.1: 937 | version "0.4.1" 938 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 939 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 940 | 941 | json-stable-stringify-without-jsonify@^1.0.1: 942 | version "1.0.1" 943 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 944 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 945 | 946 | levn@^0.4.1: 947 | version "0.4.1" 948 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" 949 | integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== 950 | dependencies: 951 | prelude-ls "^1.2.1" 952 | type-check "~0.4.0" 953 | 954 | locate-path@^3.0.0: 955 | version "3.0.0" 956 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 957 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 958 | dependencies: 959 | p-locate "^3.0.0" 960 | path-exists "^3.0.0" 961 | 962 | lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: 963 | version "4.17.21" 964 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 965 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 966 | 967 | log-symbols@3.0.0: 968 | version "3.0.0" 969 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" 970 | integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== 971 | dependencies: 972 | chalk "^2.4.2" 973 | 974 | minimatch@3.0.4, minimatch@^3.0.4: 975 | version "3.0.4" 976 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 977 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 978 | dependencies: 979 | brace-expansion "^1.1.7" 980 | 981 | minimist@^1.2.5: 982 | version "1.2.6" 983 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 984 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 985 | 986 | mkdirp@0.5.5, mkdirp@^0.5.1: 987 | version "0.5.5" 988 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 989 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 990 | dependencies: 991 | minimist "^1.2.5" 992 | 993 | mocha@^7.0.1: 994 | version "7.2.0" 995 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" 996 | integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== 997 | dependencies: 998 | ansi-colors "3.2.3" 999 | browser-stdout "1.3.1" 1000 | chokidar "3.3.0" 1001 | debug "3.2.6" 1002 | diff "3.5.0" 1003 | escape-string-regexp "1.0.5" 1004 | find-up "3.0.0" 1005 | glob "7.1.3" 1006 | growl "1.10.5" 1007 | he "1.2.0" 1008 | js-yaml "3.13.1" 1009 | log-symbols "3.0.0" 1010 | minimatch "3.0.4" 1011 | mkdirp "0.5.5" 1012 | ms "2.1.1" 1013 | node-environment-flags "1.0.6" 1014 | object.assign "4.1.0" 1015 | strip-json-comments "2.0.1" 1016 | supports-color "6.0.0" 1017 | which "1.3.1" 1018 | wide-align "1.1.3" 1019 | yargs "13.3.2" 1020 | yargs-parser "13.1.2" 1021 | yargs-unparser "1.6.0" 1022 | 1023 | ms@2.0.0: 1024 | version "2.0.0" 1025 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1026 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 1027 | 1028 | ms@2.1.1: 1029 | version "2.1.1" 1030 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 1031 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 1032 | 1033 | ms@^2.1.1: 1034 | version "2.1.2" 1035 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1036 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1037 | 1038 | natural-compare@^1.4.0: 1039 | version "1.4.0" 1040 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 1041 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= 1042 | 1043 | node-environment-flags@1.0.6: 1044 | version "1.0.6" 1045 | resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" 1046 | integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== 1047 | dependencies: 1048 | object.getownpropertydescriptors "^2.0.3" 1049 | semver "^5.7.0" 1050 | 1051 | normalize-path@^3.0.0, normalize-path@~3.0.0: 1052 | version "3.0.0" 1053 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 1054 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 1055 | 1056 | object-inspect@^1.7.0, object-inspect@^1.8.0: 1057 | version "1.8.0" 1058 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" 1059 | integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== 1060 | 1061 | object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: 1062 | version "1.1.1" 1063 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 1064 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 1065 | 1066 | object.assign@4.1.0: 1067 | version "4.1.0" 1068 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" 1069 | integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== 1070 | dependencies: 1071 | define-properties "^1.1.2" 1072 | function-bind "^1.1.1" 1073 | has-symbols "^1.0.0" 1074 | object-keys "^1.0.11" 1075 | 1076 | object.assign@^4.1.0: 1077 | version "4.1.1" 1078 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" 1079 | integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== 1080 | dependencies: 1081 | define-properties "^1.1.3" 1082 | es-abstract "^1.18.0-next.0" 1083 | has-symbols "^1.0.1" 1084 | object-keys "^1.1.1" 1085 | 1086 | object.getownpropertydescriptors@^2.0.3: 1087 | version "2.1.0" 1088 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" 1089 | integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== 1090 | dependencies: 1091 | define-properties "^1.1.3" 1092 | es-abstract "^1.17.0-next.1" 1093 | 1094 | once@^1.3.0: 1095 | version "1.4.0" 1096 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1097 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 1098 | dependencies: 1099 | wrappy "1" 1100 | 1101 | optionator@^0.9.1: 1102 | version "0.9.1" 1103 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" 1104 | integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== 1105 | dependencies: 1106 | deep-is "^0.1.3" 1107 | fast-levenshtein "^2.0.6" 1108 | levn "^0.4.1" 1109 | prelude-ls "^1.2.1" 1110 | type-check "^0.4.0" 1111 | word-wrap "^1.2.3" 1112 | 1113 | p-limit@^2.0.0: 1114 | version "2.3.0" 1115 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 1116 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 1117 | dependencies: 1118 | p-try "^2.0.0" 1119 | 1120 | p-locate@^3.0.0: 1121 | version "3.0.0" 1122 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 1123 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 1124 | dependencies: 1125 | p-limit "^2.0.0" 1126 | 1127 | p-try@^2.0.0: 1128 | version "2.2.0" 1129 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 1130 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 1131 | 1132 | parent-module@^1.0.0: 1133 | version "1.0.1" 1134 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 1135 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 1136 | dependencies: 1137 | callsites "^3.0.0" 1138 | 1139 | path-exists@^3.0.0: 1140 | version "3.0.0" 1141 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 1142 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 1143 | 1144 | path-is-absolute@^1.0.0: 1145 | version "1.0.1" 1146 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1147 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 1148 | 1149 | path-key@^3.1.0: 1150 | version "3.1.1" 1151 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 1152 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 1153 | 1154 | pathval@^1.1.0: 1155 | version "1.1.1" 1156 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 1157 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 1158 | 1159 | picomatch@^2.0.4: 1160 | version "2.2.2" 1161 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 1162 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 1163 | 1164 | prelude-ls@^1.2.1: 1165 | version "1.2.1" 1166 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 1167 | integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 1168 | 1169 | prettier-plugin-firestore-rules@^0.1.4: 1170 | version "0.1.4" 1171 | resolved "https://registry.yarnpkg.com/prettier-plugin-firestore-rules/-/prettier-plugin-firestore-rules-0.1.4.tgz#bdffcf8af70d5b9b90d80681377828db04b53197" 1172 | integrity sha512-fL5D15NvjsLNh+aUQfunwgvpP08w+lb+U1WFEV1c+ra+DYQ9eTeTz6JoVozG76KwpwVzJ0vZAzVYDsQp89ixCw== 1173 | dependencies: 1174 | prettier "^2.1.1" 1175 | 1176 | prettier@^2.1.1: 1177 | version "2.1.1" 1178 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6" 1179 | integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw== 1180 | 1181 | progress@^2.0.0: 1182 | version "2.0.3" 1183 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 1184 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 1185 | 1186 | punycode@^2.1.0: 1187 | version "2.1.1" 1188 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 1189 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 1190 | 1191 | readdirp@~3.2.0: 1192 | version "3.2.0" 1193 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" 1194 | integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== 1195 | dependencies: 1196 | picomatch "^2.0.4" 1197 | 1198 | regexpp@^3.0.0, regexpp@^3.1.0: 1199 | version "3.1.0" 1200 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" 1201 | integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== 1202 | 1203 | require-directory@^2.1.1: 1204 | version "2.1.1" 1205 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1206 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 1207 | 1208 | require-main-filename@^2.0.0: 1209 | version "2.0.0" 1210 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 1211 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 1212 | 1213 | resolve-from@^4.0.0: 1214 | version "4.0.0" 1215 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1216 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1217 | 1218 | rimraf@2.6.3: 1219 | version "2.6.3" 1220 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 1221 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 1222 | dependencies: 1223 | glob "^7.1.3" 1224 | 1225 | rimraf@^2.6.3: 1226 | version "2.7.1" 1227 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 1228 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 1229 | dependencies: 1230 | glob "^7.1.3" 1231 | 1232 | semver@^5.7.0: 1233 | version "5.7.1" 1234 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1235 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1236 | 1237 | semver@^7.2.1, semver@^7.3.2: 1238 | version "7.3.2" 1239 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" 1240 | integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== 1241 | 1242 | set-blocking@^2.0.0: 1243 | version "2.0.0" 1244 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1245 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 1246 | 1247 | shebang-command@^2.0.0: 1248 | version "2.0.0" 1249 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1250 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1251 | dependencies: 1252 | shebang-regex "^3.0.0" 1253 | 1254 | shebang-regex@^3.0.0: 1255 | version "3.0.0" 1256 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1257 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1258 | 1259 | slice-ansi@^2.1.0: 1260 | version "2.1.0" 1261 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" 1262 | integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== 1263 | dependencies: 1264 | ansi-styles "^3.2.0" 1265 | astral-regex "^1.0.0" 1266 | is-fullwidth-code-point "^2.0.0" 1267 | 1268 | sprintf-js@~1.0.2: 1269 | version "1.0.3" 1270 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1271 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 1272 | 1273 | "string-width@^1.0.2 || 2": 1274 | version "2.1.1" 1275 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1276 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1277 | dependencies: 1278 | is-fullwidth-code-point "^2.0.0" 1279 | strip-ansi "^4.0.0" 1280 | 1281 | string-width@^3.0.0, string-width@^3.1.0: 1282 | version "3.1.0" 1283 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 1284 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 1285 | dependencies: 1286 | emoji-regex "^7.0.1" 1287 | is-fullwidth-code-point "^2.0.0" 1288 | strip-ansi "^5.1.0" 1289 | 1290 | string.prototype.trimend@^1.0.1: 1291 | version "1.0.1" 1292 | resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" 1293 | integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== 1294 | dependencies: 1295 | define-properties "^1.1.3" 1296 | es-abstract "^1.17.5" 1297 | 1298 | string.prototype.trimstart@^1.0.1: 1299 | version "1.0.1" 1300 | resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" 1301 | integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== 1302 | dependencies: 1303 | define-properties "^1.1.3" 1304 | es-abstract "^1.17.5" 1305 | 1306 | strip-ansi@^4.0.0: 1307 | version "4.0.0" 1308 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1309 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 1310 | dependencies: 1311 | ansi-regex "^3.0.0" 1312 | 1313 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: 1314 | version "5.2.0" 1315 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 1316 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 1317 | dependencies: 1318 | ansi-regex "^4.1.0" 1319 | 1320 | strip-ansi@^6.0.0: 1321 | version "6.0.0" 1322 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 1323 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 1324 | dependencies: 1325 | ansi-regex "^5.0.0" 1326 | 1327 | strip-json-comments@2.0.1: 1328 | version "2.0.1" 1329 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1330 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 1331 | 1332 | strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: 1333 | version "3.1.1" 1334 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1335 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1336 | 1337 | supports-color@6.0.0: 1338 | version "6.0.0" 1339 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" 1340 | integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== 1341 | dependencies: 1342 | has-flag "^3.0.0" 1343 | 1344 | supports-color@^5.3.0: 1345 | version "5.5.0" 1346 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1347 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1348 | dependencies: 1349 | has-flag "^3.0.0" 1350 | 1351 | supports-color@^7.1.0: 1352 | version "7.2.0" 1353 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1354 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1355 | dependencies: 1356 | has-flag "^4.0.0" 1357 | 1358 | table@^5.2.3: 1359 | version "5.4.6" 1360 | resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" 1361 | integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== 1362 | dependencies: 1363 | ajv "^6.10.2" 1364 | lodash "^4.17.14" 1365 | slice-ansi "^2.1.0" 1366 | string-width "^3.0.0" 1367 | 1368 | text-table@^0.2.0: 1369 | version "0.2.0" 1370 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1371 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 1372 | 1373 | to-regex-range@^5.0.1: 1374 | version "5.0.1" 1375 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1376 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1377 | dependencies: 1378 | is-number "^7.0.0" 1379 | 1380 | tslib@^1.8.1: 1381 | version "1.13.0" 1382 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" 1383 | integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== 1384 | 1385 | tsutils@^3.17.1: 1386 | version "3.17.1" 1387 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" 1388 | integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== 1389 | dependencies: 1390 | tslib "^1.8.1" 1391 | 1392 | type-check@^0.4.0, type-check@~0.4.0: 1393 | version "0.4.0" 1394 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" 1395 | integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 1396 | dependencies: 1397 | prelude-ls "^1.2.1" 1398 | 1399 | type-detect@^4.0.0, type-detect@^4.0.5: 1400 | version "4.0.8" 1401 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 1402 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 1403 | 1404 | type-fest@^0.8.1: 1405 | version "0.8.1" 1406 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" 1407 | integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== 1408 | 1409 | typescript@^3.3.1: 1410 | version "3.9.7" 1411 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" 1412 | integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== 1413 | 1414 | uri-js@^4.2.2: 1415 | version "4.4.0" 1416 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" 1417 | integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== 1418 | dependencies: 1419 | punycode "^2.1.0" 1420 | 1421 | v8-compile-cache@^2.0.3: 1422 | version "2.1.1" 1423 | resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" 1424 | integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== 1425 | 1426 | vscode-test@^1.0.2: 1427 | version "1.4.0" 1428 | resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.4.0.tgz#a56f73c1667b4d37ba6baa6765f233a19d4ffbfe" 1429 | integrity sha512-Jt7HNGvSE0+++Tvtq5wc4hiXLIr2OjDShz/gbAfM/mahQpy4rKBnmOK33D+MR67ATWviQhl+vpmU3p/qwSH/Pg== 1430 | dependencies: 1431 | http-proxy-agent "^2.1.0" 1432 | https-proxy-agent "^2.2.4" 1433 | rimraf "^2.6.3" 1434 | 1435 | vscode-textmate@^5.1.1: 1436 | version "5.2.0" 1437 | resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" 1438 | integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== 1439 | 1440 | which-module@^2.0.0: 1441 | version "2.0.0" 1442 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 1443 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 1444 | 1445 | which@1.3.1: 1446 | version "1.3.1" 1447 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1448 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 1449 | dependencies: 1450 | isexe "^2.0.0" 1451 | 1452 | which@^2.0.1: 1453 | version "2.0.2" 1454 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1455 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1456 | dependencies: 1457 | isexe "^2.0.0" 1458 | 1459 | wide-align@1.1.3: 1460 | version "1.1.3" 1461 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 1462 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 1463 | dependencies: 1464 | string-width "^1.0.2 || 2" 1465 | 1466 | word-wrap@^1.2.3: 1467 | version "1.2.3" 1468 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" 1469 | integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== 1470 | 1471 | wrap-ansi@^5.1.0: 1472 | version "5.1.0" 1473 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 1474 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== 1475 | dependencies: 1476 | ansi-styles "^3.2.0" 1477 | string-width "^3.0.0" 1478 | strip-ansi "^5.0.0" 1479 | 1480 | wrappy@1: 1481 | version "1.0.2" 1482 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1483 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1484 | 1485 | write@1.0.3: 1486 | version "1.0.3" 1487 | resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" 1488 | integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== 1489 | dependencies: 1490 | mkdirp "^0.5.1" 1491 | 1492 | y18n@^4.0.0: 1493 | version "4.0.1" 1494 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" 1495 | integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== 1496 | 1497 | yargs-parser@13.1.2, yargs-parser@^13.1.2: 1498 | version "13.1.2" 1499 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" 1500 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== 1501 | dependencies: 1502 | camelcase "^5.0.0" 1503 | decamelize "^1.2.0" 1504 | 1505 | yargs-unparser@1.6.0: 1506 | version "1.6.0" 1507 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" 1508 | integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== 1509 | dependencies: 1510 | flat "^4.1.0" 1511 | lodash "^4.17.15" 1512 | yargs "^13.3.0" 1513 | 1514 | yargs@13.3.2, yargs@^13.3.0: 1515 | version "13.3.2" 1516 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" 1517 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== 1518 | dependencies: 1519 | cliui "^5.0.0" 1520 | find-up "^3.0.0" 1521 | get-caller-file "^2.0.1" 1522 | require-directory "^2.1.1" 1523 | require-main-filename "^2.0.0" 1524 | set-blocking "^2.0.0" 1525 | string-width "^3.0.0" 1526 | which-module "^2.0.0" 1527 | y18n "^4.0.0" 1528 | yargs-parser "^13.1.2" 1529 | --------------------------------------------------------------------------------