diff --git a/.coffeelintignore b/.coffeelintignore deleted file mode 100644 index 1db51fed..00000000 --- a/.coffeelintignore +++ /dev/null @@ -1 +0,0 @@ -spec/fixtures diff --git a/available-snippets-view.js b/available-snippets-view.js new file mode 100644 index 00000000..fc476d6b --- /dev/null +++ b/available-snippets-view.js @@ -0,0 +1,43 @@ +const SelectListView = require('atom-select-list') + +module.exports = class AvailableSnippetsView extends SelectListView { + constructor (snippets, editor) { + super({ + items: Object.entries(snippets.snippetsByScopes() + .getPropertyValue(editor.getRootScopeDescriptor().getScopeChain())), + filterKeyForItem: ([name, { prefix }]) => prefix + name, + elementForItem: ([name, { prefix, description }]) => { + const li = document.createElement('li') + li.classList.add('two-lines') + + const primaryLine = li.appendChild(document.createElement('div')) + primaryLine.classList.add('primary-line') + primaryLine.textContent = prefix + + const secondaryLine = li.appendChild(document.createElement('div')) + secondaryLine.classList.add('secondary-line') + // TODO: Nullish coalescing operator + secondaryLine.textContent = description != null ? description : name + + return li + }, + emptyMessage: 'No snippets defined for this Grammar.', + itemsClassList: ['available-snippets'], + didConfirmSelection: ([, { body }]) => { + this.destroy() + editor.getCursors().forEach(cursor => + snippets.parse(body).expand({ editor, cursor })) + }, + didConfirmEmptySelection: () => this.destroy(), + didCancelSelection: () => this.destroy() + }) + + const panel = atom.workspace.addModalPanel({ item: this }) + this.disposables.add( + // Register cleanup disposables to be called on desctruction + { dispose: () => document.activeElement.focus }, + { dispose: () => { panel.destroy() } }) + + this.focus() + } +} diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 00000000..f0525c14 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-private-methods" + ] +} diff --git a/coffeelint.json b/coffeelint.json deleted file mode 100644 index a5dd715e..00000000 --- a/coffeelint.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "max_line_length": { - "level": "ignore" - }, - "no_empty_param_list": { - "level": "error" - }, - "arrow_spacing": { - "level": "error" - }, - "no_interpolation_in_single_quotes": { - "level": "error" - }, - "no_debugger": { - "level": "error" - }, - "prefer_english_operator": { - "level": "error" - }, - "colon_assignment_spacing": { - "spacing": { - "left": 0, - "right": 1 - }, - "level": "error" - }, - "braces_spacing": { - "spaces": 0, - "level": "error" - }, - "spacing_after_comma": { - "level": "error" - }, - "no_stand_alone_at": { - "level": "error" - } -} diff --git a/expression/babel.config.json b/expression/babel.config.json new file mode 100644 index 00000000..f0525c14 --- /dev/null +++ b/expression/babel.config.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-private-methods" + ] +} diff --git a/expression/choice.js b/expression/choice.js new file mode 100644 index 00000000..cc361406 --- /dev/null +++ b/expression/choice.js @@ -0,0 +1,60 @@ +const { CompositeDisposable } = require('atom') + +const Expression = require('./expression') + +module.exports = class Choice extends Expression { + constructor (identifier, choices) { + super(identifier) + + this.choices = choices + // The "first" and therefore default values is last in the list so that + // choice cycling starts from the second choice + this.default = choices[choices.length - 1] + } + + activate (editor, cursor, stop, mirror) { + super.activate(editor, cursor, stop, mirror) + // Don't bother if a mirror, the marker won't get iterated over + if (mirror === stop) { + const disposables = new CompositeDisposable() + + const target = 'atom-text-editor:not([mini])' + const cycle = `snippets:next-choice-${stop.id}` + + const choices = { + choices: this.choices, + iterator: this.choices.values(), + next () { + const iteration = this.iterator.next() + const { value } = iteration.done + ? (this.iterator = this.choices.values()).next() + : iteration + editor.getBuffer().setTextInRange(stop.getBufferRange(), value) + cursor.selection.setBufferRange(stop.getBufferRange()) + } + } + + // What happens when the user clicks inside the choice, resulting in it nolonger being selected + disposables.add( + atom.keymaps.add(module.filename, { [target]: { 'shift-tab': cycle } }), + atom.commands.add(target, cycle, event => choices.next()), + cursor.onDidChangePosition(({ newBufferPosition }) => { + if (!stop.getBufferRange().containsPoint(newBufferPosition)) { + disposables.dispose() + } + })) + } + } + + expand (editor, cursor, tabstops, variables) { + if (!(this.identifier in variables)) { + this.mark({ tabstops, ...this.insert(editor, cursor, this.default) }) + } else { + super.expand(editor, cursor, tabstops, variables) + } + } + + toString () { + return this.default.toString() + } +} diff --git a/expression/expression.js b/expression/expression.js new file mode 100644 index 00000000..92367ac0 --- /dev/null +++ b/expression/expression.js @@ -0,0 +1,45 @@ +module.exports = class Expression { + constructor (identifier) { + this.identifier = identifier + } + + expand (editor, cursor, tabstops, variables) { + // Check whether we are a tabstop or a variable + Number.isInteger(this.identifier) + // Create a tabstop marker at our position + ? this.mark({ tabstops, start: cursor.getBufferPosition() }) + // Check whether we are a know variable or not + : this.identifier in variables + // Insert the variables value + ? this.insert(editor, cursor, variables[this.identifier]) + // Insert 'this.identifier' and create a tabstop marker with it selected + : this.mark({ tabstops, ...this.insert(editor, cursor, this.identifier) }) + } + + insert (editor, cursor, value) { + return editor.getBuffer().insert(cursor.getBufferPosition(), value) + } + + activate (editor, cursor, stop, mirror) { + if (mirror === stop) { + cursor.selection.setBufferRange(stop.getBufferRange()) + const subscription = cursor.onDidChangePosition(({ newBufferPosition }) => { + if (!stop.getBufferRange().containsPoint(newBufferPosition)) { + stop.destroy() + subscription.dispose() + } + }) + } else { + editor.decorateMarker(mirror, { type: 'highlight' }) + stop.onDidDestroy(() => mirror.destroy()) + } + } + + mark ({ tabstops, start, end = start, exclusive = true, expression = this }) { + tabstops.markBufferRange({ start, end }, { exclusive }).setProperties({ expression }) + } + + toString () { + return '' + } +} diff --git a/expression/placeholder.js b/expression/placeholder.js new file mode 100644 index 00000000..5ef36e8d --- /dev/null +++ b/expression/placeholder.js @@ -0,0 +1,21 @@ +const Expression = require('./expression') + +module.exports = class Placeholder extends Expression { + constructor (identifier, snippet) { + super(identifier) + + this.snippet = snippet + } + + expand (editor, cursor, tabstops, variables) { + if (!(this.identifier in variables)) { + this.mark({ tabstops, ...this.snippet.expand({ editor, cursor, tabstops, variables }) }) + } else { + super.expand(editor, cursor, tabstops, variables) + } + } + + toString () { + return this.snippet.toString() + } +} diff --git a/expression/snippet.js b/expression/snippet.js new file mode 100644 index 00000000..2dec54e9 --- /dev/null +++ b/expression/snippet.js @@ -0,0 +1,185 @@ +const { CompositeDisposable } = require('atom') +const path = require('path') + +const Expression = require('./expression') + +module.exports = class Snippet extends Expression { + static VARIABLES = { + // The currently selected text or the empty string + TM_SELECTED_TEXT: (editor, cursor) => cursor.selection.getText(), + // The contents of the current line + TM_CURRENT_LINE: (editor, cursor) => cursor.getCurrentBufferLine(), + // The contents of the word under cursor or the empty string + TM_CURRENT_WORD: (editor, cursor) => editor.getTextInBufferRange(cursor.getCurrentWordBufferRange()), + // The zero-index based line number + TM_LINE_INDEX: (editor, cursor) => cursor.getBufferRow().toString(), + // The one-index based line number + // Does 'getScreenRow'work as intended? + TM_LINE_NUMBER: (editor, cursor) => (cursor.getScreenRow() + 1).toString(), + // The filename of the current document + TM_FILENAME: (editor, cursor) => editor.getTitle(), + // The filename of the current document without its extensions + TM_FILENAME_BASE: (editor, cursor, filepath = editor.getTitle()) => path.basename(filepath, path.extname(filepath)), + // The directory of the current document + TM_DIRECTORY: (editor, cursor) => path.dirname(editor.getPath()), + // The full file path of the current document + TM_FILEPATH: (editor, cursor) => editor.getPath(), + // The contents of the clipboard + CLIPBOARD: (editor, cursor) => atom.clipboard.read(), + // The name of the opened workspace or folder + WORKSPACE_NAME: (editor, cursor, [projectPath] = atom.project.relativizePath(editor.getPath())) => path.basename(projectPath), + // Insert the current date and time + // The current year + CURRENT_YEAR: (editor, cursor) => new Date().toLocaleString('default', { year: 'numeric' }), + // The current year's last two digits + CURRENT_YEAR_SHORT: (editor, cursor) => new Date().toLocaleString('default', { year: '2-digit' }), + // The month as two digits + CURRENT_MONTH: (editor, cursor) => new Date().toLocaleString('default', { month: '2-digit' }), + // The full name of the month + CURRENT_MONTH_NAME: (editor, cursor) => new Date().toLocaleString('default', { month: 'long' }), + // The short name of the month + CURRENT_MONTH_NAME_SHORT: (editor, cursor) => new Date().toLocaleString('default', { month: 'short' }), + // The day of the month + CURRENT_DATE: (editor, cursor) => new Date().toLocaleString('default', { day: '2-digit' }), + // The name of day + CURRENT_DAY_NAME: (editor, cursor) => new Date().toLocaleString('default', { weekday: 'long' }), + // The short name of the day + CURRENT_DAY_NAME_SHORT: (editor, cursor) => new Date().toLocaleString('default', { weekday: 'short' }), + // The current hour in 24-hour clock format + CURRENT_HOUR: (editor, cursor) => new Date().toLocaleString('default', { hour: '2-digit' }), + // The current minute + CURRENT_MINUTE: (editor, cursor) => new Date().toLocaleString('default', { minute: '2-digit' }), + // The current second + CURRENT_SECOND: (editor, cursor) => new Date().toLocaleString('default', { second: '2-digit' }), + // The number of seconds since the Unix epoch + CURRENT_SECONDS_UNIX: (editor, cursor) => Math.round(new Date() / 1000).toString(), + // + // TODO?: + // Insert line or block comments, honoring the current language + // BLOCK_COMMENT_START + // BLOCK_COMMENT_END + // LINE_COMMENT + // + // custom = custom variables + // TODO: I dont remember what this cache is supposed to do + PROXY: (editor, cursor, custom) => new Proxy({}, { + get: (cache, property) => property in this.VARIABLES + ? (cache[property] = this.VARIABLES[property](editor, cursor)) + : property in custom + ? custom[property] + // We should never see this value used + : null, + has: (cache, property) => property in this.VARIABLES || property in custom + }) + } + + static getTabstops (markers) { + const tabstops = [] + const unknowns = [] + + markers.forEach(marker => { + const { expression } = marker.getProperties() + + Number.isInteger(expression.identifier) + ? Array.isArray(tabstops[expression.identifier]) + ? tabstops[expression.identifier].push(marker) + : tabstops[expression.identifier] = [marker] + : unknowns.push([marker]) + }) + // Include all unknown variables at the end + if (unknowns.length) { + tabstops.push(...unknowns) + } + // Move 0th tabstop to last + tabstops.push(tabstops.shift()) + + return tabstops + } + + constructor (body) { + // This snippet will work as the default ending tabstop + super(0) + + this.body = body + } + + // We work as the default ending tabstop, this is a special case + activate (editor, cursor, stop, mirror) { + cursor.setBufferPosition(stop.getBufferRange().end) + } + + expand ({ + editor = atom.workspace.getActiveTextEditor(), + cursor = editor.getLastCursor(), + tabstops = editor.addMarkerLayer(), // This is a _Display_MarkerLayer + variables = {} + } = {}) { + if (this.legacySyntax) { + atom.notifications.addWarning('Snippets: Snippet uses deprecated syntax.', { + description: 'Old syntactic features will be removed in a future release', + dismissable: true + }) + } + + // Expression a variable proxy to access given and built-in variables + variables = Snippet.VARIABLES.PROXY(editor, cursor, variables) + + // Define a marker that spans the whole snippet + // This will also be used as the ending tabstop if there isn't an explicit one + // Don't make this marker exclusive, so it expands with the inserts bellow + this.mark({ tabstops, start: cursor.getBufferPosition(), exclusive: false }) + const marker = tabstops.getMarkers().pop() + // Take care that our disposables are disposed if necessary + const disposables = new CompositeDisposable( + cursor.onDidDestroy(() => disposables.dispose()), + cursor.onDidChangePosition(({ newBufferPosition }) => { + // Exclude endpoints, so that end tabstops don't trigger mirror logic + if (!marker.getBufferRange().containsPoint(newBufferPosition, true)) { + disposables.dispose() + } + })) + + // We are the outmost snippet + const parentSnippet = tabstops.getMarkerCount() === 1 + + this.body.forEach(value => value instanceof Object + ? value.expand(editor, cursor, tabstops, variables) + : this.insert(editor, cursor, value)) + + // Only create tabstop stuff if we have any + if (parentSnippet && tabstops.getMarkerCount() > 1) { + const target = 'atom-text-editor:not([mini])' + const iterate = `snippets:next-tab-stop-${tabstops.id}` + // The markers aren't guaranteed to be in insertion order, as they're stored in an Object + // Luckilly the ids used are integers and the values are fetched using 'Object.values' + const stops = { + iterator: Snippet.getTabstops(tabstops.getMarkers()).values(), + next () { + const iteration = this.iterator.next() + if (!iteration.done) { + const { value: [stop] } = iteration + const { expression } = stop.getProperties() + iteration.value.forEach(mirror => expression.activate(editor, cursor, stop, mirror)) + return true + } + disposables.dispose() + } + } + + disposables.add( + { dispose: () => tabstops.destroy() }, + atom.keymaps.add(module.filename, { [target]: { tab: iterate } }), + atom.commands.add(target, iterate, event => stops.next() || + event.abortKeyBinding())) + + // Go to the first tabstop + stops.next() + } + + return marker.getBufferRange() + } + + toString () { + return this.body.reduce((result, value) => result + value) + } +} diff --git a/expression/transformation.js b/expression/transformation.js new file mode 100644 index 00000000..2d76d1a9 --- /dev/null +++ b/expression/transformation.js @@ -0,0 +1,40 @@ +const Expression = require('./expression') + +module.exports = class Transformation extends Expression { + constructor (identifier, [regexp, format, flags]) { + super(identifier) + + this.regexp = new RegExp(regexp, flags.join('')) + this.format = format + } + + activate (editor, cursor, stop, mirror) { + super.activate(editor, cursor, stop, mirror) + mirror.onDidDestroy(() => { + const range = mirror.getBufferRange() + const buffer = editor.getBuffer() + buffer.setTextInRange(range, this.transform(buffer.getTextInRange(range))) + }) + } + + transform (string, regexp = this.regexp) { + let fold = sequence => sequence + return this.format.reduce((result, sequence) => { + const [group, insertion, replacement = ''] = sequence + sequence instanceof Function + ? fold = sequence + : sequence instanceof Object + ? result += fold(string.replace(regexp, group) ? insertion : replacement) + : result += fold(string.replace(regexp, sequence)) + return result + }, '') + } + + insert (editor, cursor, value) { + return super.insert(editor, cursor, this.transform(value)) + } + + toString () { + return this.transform(super.toString()) + } +} diff --git a/keymaps/snippets-1.cson b/keymaps/snippets-1.cson deleted file mode 100644 index ac786f45..00000000 --- a/keymaps/snippets-1.cson +++ /dev/null @@ -1,2 +0,0 @@ -'atom-text-editor:not([mini])': - 'tab': 'snippets:expand' diff --git a/keymaps/snippets-2.cson b/keymaps/snippets-2.cson deleted file mode 100644 index 1ce10c9b..00000000 --- a/keymaps/snippets-2.cson +++ /dev/null @@ -1,6 +0,0 @@ -# it's critical that these bindings be loaded after those snippets-1 so they -# are later in the cascade, hence breaking the keymap into 2 files - -'atom-text-editor:not([mini])': - 'tab': 'snippets:next-tab-stop' - 'shift-tab': 'snippets:previous-tab-stop' diff --git a/lib/editor-store.js b/lib/editor-store.js deleted file mode 100644 index c57cb7ad..00000000 --- a/lib/editor-store.js +++ /dev/null @@ -1,73 +0,0 @@ -const SnippetHistoryProvider = require('./snippet-history-provider') - -class EditorStore { - constructor (editor) { - this.editor = editor - this.buffer = this.editor.getBuffer() - this.observer = null - this.checkpoint = null - this.expansions = [] - this.existingHistoryProvider = null - } - - getExpansions () { - return this.expansions - } - - setExpansions (list) { - this.expansions = list - } - - clearExpansions () { - this.expansions = [] - } - - addExpansion (snippetExpansion) { - this.expansions.push(snippetExpansion) - } - - observeHistory (delegates) { - if (this.existingHistoryProvider == null) { - this.existingHistoryProvider = this.buffer.historyProvider - } - - const newProvider = SnippetHistoryProvider(this.existingHistoryProvider, delegates) - this.buffer.setHistoryProvider(newProvider) - } - - stopObservingHistory (editor) { - if (this.existingHistoryProvider == null) { return } - this.buffer.setHistoryProvider(this.existingHistoryProvider) - this.existingHistoryProvider = null - } - - observe (callback) { - if (this.observer != null) { this.observer.dispose() } - this.observer = this.buffer.onDidChangeText(callback) - } - - stopObserving () { - if (this.observer == null) { return false } - this.observer.dispose() - this.observer = null - return true - } - - makeCheckpoint () { - const existing = this.checkpoint - if (existing) { - this.buffer.groupChangesSinceCheckpoint(existing) - } - this.checkpoint = this.buffer.createCheckpoint() - } -} - -EditorStore.store = new WeakMap() -EditorStore.findOrCreate = function (editor) { - if (!this.store.has(editor)) { - this.store.set(editor, new EditorStore(editor)) - } - return this.store.get(editor) -} - -module.exports = EditorStore diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index 0814a3df..00000000 --- a/lib/helpers.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @babel */ - -import path from 'path' - -export function getPackageRoot() { - const {resourcePath} = atom.getLoadSettings() - const currentFileWasRequiredFromSnapshot = !path.isAbsolute(__dirname) - if (currentFileWasRequiredFromSnapshot) { - return path.join(resourcePath, 'node_modules', 'snippets') - } else { - return path.resolve(__dirname, '..') - } -} diff --git a/lib/insertion.js b/lib/insertion.js deleted file mode 100644 index 96065d1e..00000000 --- a/lib/insertion.js +++ /dev/null @@ -1,94 +0,0 @@ -const ESCAPES = { - u: (flags) => { - flags.lowercaseNext = false - flags.uppercaseNext = true - }, - l: (flags) => { - flags.uppercaseNext = false - flags.lowercaseNext = true - }, - U: (flags) => { - flags.lowercaseAll = false - flags.uppercaseAll = true - }, - L: (flags) => { - flags.uppercaseAll = false - flags.lowercaseAll = true - }, - E: (flags) => { - flags.uppercaseAll = false - flags.lowercaseAll = false - }, - r: (flags, result) => { - result.push('\\r') - }, - n: (flags, result) => { - result.push('\\n') - }, - $: (flags, result) => { - result.push('$') - } -} - -function transformText (str, flags) { - if (flags.uppercaseAll) { - return str.toUpperCase() - } else if (flags.lowercaseAll) { - return str.toLowerCase() - } else if (flags.uppercaseNext) { - flags.uppercaseNext = false - return str.replace(/^./, s => s.toUpperCase()) - } else if (flags.lowercaseNext) { - return str.replace(/^./, s => s.toLowerCase()) - } - return str -} - -class Insertion { - constructor ({ range, substitution }) { - this.range = range - this.substitution = substitution - if (substitution) { - if (substitution.replace === undefined) { - substitution.replace = '' - } - this.replacer = this.makeReplacer(substitution.replace) - } - } - - isTransformation () { - return !!this.substitution - } - - makeReplacer (replace) { - return function replacer (...match) { - let flags = { - uppercaseAll: false, - lowercaseAll: false, - uppercaseNext: false, - lowercaseNext: false - } - replace = [...replace] - let result = [] - replace.forEach(token => { - if (typeof token === 'string') { - result.push(transformText(token, flags)) - } else if (token.escape) { - ESCAPES[token.escape](flags, result) - } else if (token.backreference) { - let transformed = transformText(match[token.backreference], flags) - result.push(transformed) - } - }) - return result.join('') - } - } - - transform (input) { - let { substitution } = this - if (!substitution) { return input } - return input.replace(substitution.find, this.replacer) - } -} - -module.exports = Insertion diff --git a/lib/snippet-body-parser.js b/lib/snippet-body-parser.js deleted file mode 100644 index d4293ecb..00000000 --- a/lib/snippet-body-parser.js +++ /dev/null @@ -1,14 +0,0 @@ -let parser -try { - parser = require('./snippet-body') -} catch (error) { - const {allowUnsafeEval} = require('loophole') - const fs = require('fs-plus') - const PEG = require('pegjs') - - const grammarSrc = fs.readFileSync(require.resolve('./snippet-body.pegjs'), 'utf8') - parser = null - allowUnsafeEval(() => parser = PEG.buildParser(grammarSrc)) -} - -module.exports = parser diff --git a/lib/snippet-body.pegjs b/lib/snippet-body.pegjs deleted file mode 100644 index 476c65af..00000000 --- a/lib/snippet-body.pegjs +++ /dev/null @@ -1,82 +0,0 @@ -{ - // Joins all consecutive strings in a collection without clobbering any - // non-string members. - function coalesce (parts) { - const result = []; - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - const ri = result.length - 1; - if (typeof part === 'string' && typeof result[ri] === 'string') { - result[ri] = result[ri] + part; - } else { - result.push(part); - } - } - return result; - } - - function flatten (parts) { - return parts.reduce(function (flat, rest) { - return flat.concat(Array.isArray(rest) ? flatten(rest) : rest); - }, []); - } -} -bodyContent = content:(tabStop / bodyContentText)* { return content; } -bodyContentText = text:bodyContentChar+ { return text.join(''); } -bodyContentChar = escaped / !tabStop char:. { return char; } - -escaped = '\\' char:. { return char; } -tabStop = tabStopWithTransformation / tabStopWithPlaceholder / tabStopWithoutPlaceholder / simpleTabStop - -simpleTabStop = '$' index:[0-9]+ { - return { index: parseInt(index.join("")), content: [] }; -} -tabStopWithoutPlaceholder = '${' index:[0-9]+ '}' { - return { index: parseInt(index.join("")), content: [] }; -} -tabStopWithPlaceholder = '${' index:[0-9]+ ':' content:placeholderContent '}' { - return { index: parseInt(index.join("")), content: content }; -} -tabStopWithTransformation = '${' index:[0-9]+ substitution:transformationSubstitution '}' { - return { - index: parseInt(index.join(""), 10), - content: [], - substitution: substitution - }; -} - -placeholderContent = content:(tabStop / placeholderContentText / variable )* { return flatten(content); } -placeholderContentText = text:placeholderContentChar+ { return coalesce(text); } -placeholderContentChar = escaped / placeholderVariableReference / !tabStop !variable char:[^}] { return char; } - -placeholderVariableReference = '$' digit:[0-9]+ { - return { index: parseInt(digit.join(""), 10), content: [] }; -} - -variable = '${' variableContent '}' { - return ''; // we eat variables and do nothing with them for now -} -variableContent = content:(variable / variableContentText)* { return content; } -variableContentText = text:variableContentChar+ { return text.join(''); } -variableContentChar = !variable char:('\\}' / [^}]) { return char; } - -escapedForwardSlash = pair:'\\/' { return pair; } - -// A pattern and replacement for a transformed tab stop. -transformationSubstitution = '/' find:(escapedForwardSlash / [^/])* '/' replace:formatString* '/' flags:[imy]* { - let reFind = new RegExp(find.join(''), flags.join('') + 'g'); - return { find: reFind, replace: replace[0] }; -} - -formatString = content:(formatStringEscape / formatStringReference / escapedForwardSlash / [^/])+ { - return content; -} -// Backreferencing a substitution. Different from a tab stop. -formatStringReference = '$' digits:[0-9]+ { - return { backreference: parseInt(digits.join(''), 10) }; -}; -// One of the special control flags in a format string for case folding and -// other tasks. -formatStringEscape = '\\' flag:[ULulErn$] { - return { escape: flag }; -} diff --git a/lib/snippet-expansion.js b/lib/snippet-expansion.js deleted file mode 100644 index 54a525bc..00000000 --- a/lib/snippet-expansion.js +++ /dev/null @@ -1,245 +0,0 @@ -const {CompositeDisposable, Range, Point} = require('atom') - -module.exports = class SnippetExpansion { - constructor(snippet, editor, cursor, snippets) { - this.settingTabStop = false - this.isIgnoringBufferChanges = false - this.onUndoOrRedo = this.onUndoOrRedo.bind(this) - this.snippet = snippet - this.editor = editor - this.cursor = cursor - this.snippets = snippets - this.subscriptions = new CompositeDisposable - this.tabStopMarkers = [] - this.selections = [this.cursor.selection] - - const startPosition = this.cursor.selection.getBufferRange().start - let {body, tabStopList} = this.snippet - let tabStops = tabStopList.toArray() - - let indent = this.editor.lineTextForBufferRow(startPosition.row).match(/^\s*/)[0] - if (this.snippet.lineCount > 1 && indent) { - // Add proper leading indentation to the snippet - body = body.replace(/\n/g, `\n${indent}`) - - tabStops = tabStops.map(tabStop => tabStop.copyWithIndent(indent)) - } - - this.editor.transact(() => { - this.ignoringBufferChanges(() => { - this.editor.transact(() => { - const newRange = this.cursor.selection.insertText(body, {autoIndent: false}) - if (this.snippet.tabStopList.length > 0) { - this.subscriptions.add(this.cursor.onDidChangePosition(event => this.cursorMoved(event))) - this.subscriptions.add(this.cursor.onDidDestroy(() => this.cursorDestroyed())) - this.placeTabStopMarkers(startPosition, tabStops) - this.snippets.addExpansion(this.editor, this) - this.editor.normalizeTabsInBufferRange(newRange) - } - }) - }) - }) - } - - // Set a flag on undo or redo so that we know not to re-apply transforms. - // They're already accounted for in the history. - onUndoOrRedo (isUndo) { - this.isUndoingOrRedoing = true - } - - cursorMoved ({oldBufferPosition, newBufferPosition, textChanged}) { - if (this.settingTabStop || textChanged) { return } - const itemWithCursor = this.tabStopMarkers[this.tabStopIndex].find(item => item.marker.getBufferRange().containsPoint(newBufferPosition)) - - if (itemWithCursor && !itemWithCursor.insertion.isTransformation()) { return } - - this.destroy() - } - - cursorDestroyed () { if (!this.settingTabStop) { this.destroy() } } - - textChanged (event) { - if (this.isIgnoringBufferChanges) { return } - - // Don't try to alter the buffer if all we're doing is restoring a - // snapshot from history. - if (this.isUndoingOrRedoing) { - this.isUndoingOrRedoing = false - return - } - - this.applyTransformations(this.tabStopIndex) - } - - ignoringBufferChanges (callback) { - const wasIgnoringBufferChanges = this.isIgnoringBufferChanges - this.isIgnoringBufferChanges = true - callback() - this.isIgnoringBufferChanges = wasIgnoringBufferChanges - } - - applyAllTransformations () { - this.editor.transact(() => { - this.tabStopMarkers.forEach((item, index) => - this.applyTransformations(index, true)) - }) - } - - applyTransformations (tabStop, initial = false) { - const items = [...this.tabStopMarkers[tabStop]] - if (items.length === 0) { return } - - const primary = items.shift() - const primaryRange = primary.marker.getBufferRange() - const inputText = this.editor.getTextInBufferRange(primaryRange) - - this.ignoringBufferChanges(() => { - for (const item of items) { - const {marker, insertion} = item - var range = marker.getBufferRange() - - // Don't transform mirrored tab stops. They have their own cursors, so - // mirroring happens automatically. - if (!insertion.isTransformation()) { continue } - - var outputText = insertion.transform(inputText) - this.editor.transact(() => this.editor.setTextInBufferRange(range, outputText)) - const newRange = new Range( - range.start, - range.start.traverse(new Point(0, outputText.length)) - ) - marker.setBufferRange(newRange) - } - }) - } - - placeTabStopMarkers (startPosition, tabStops) { - for (const tabStop of tabStops) { - const {insertions} = tabStop - const markers = [] - - if (!tabStop.isValid()) { continue } - - for (const insertion of insertions) { - const {range} = insertion - const {start, end} = range - const marker = this.getMarkerLayer(this.editor).markBufferRange([ - startPosition.traverse(start), - startPosition.traverse(end) - ]) - markers.push({ - index: markers.length, - marker, - insertion - }) - } - - this.tabStopMarkers.push(markers) - } - - this.setTabStopIndex(0) - this.applyAllTransformations() - } - - goToNextTabStop () { - const nextIndex = this.tabStopIndex + 1 - if (nextIndex < this.tabStopMarkers.length) { - if (this.setTabStopIndex(nextIndex)) { - return true - } else { - return this.goToNextTabStop() - } - } else { - // The user has tabbed past the last tab stop. If the last tab stop is a - // $0, we shouldn't move the cursor any further. - if (this.snippet.tabStopList.hasEndStop) { - this.destroy() - return false - } else { - const succeeded = this.goToEndOfLastTabStop() - this.destroy() - return succeeded - } - } - } - - goToPreviousTabStop () { - if (this.tabStopIndex > 0) { this.setTabStopIndex(this.tabStopIndex - 1) } - } - - setTabStopIndex (tabStopIndex) { - this.tabStopIndex = tabStopIndex - this.settingTabStop = true - let markerSelected = false - - const items = this.tabStopMarkers[this.tabStopIndex] - if (items.length === 0) { return false } - - const ranges = [] - this.hasTransforms = false - for (const item of items) { - const {marker, insertion} = item - if (marker.isDestroyed()) { continue } - if (!marker.isValid()) { continue } - if (insertion.isTransformation()) { - this.hasTransforms = true - continue - } - ranges.push(marker.getBufferRange()) - } - - if (ranges.length > 0) { - for (const selection of this.selections.slice(ranges.length)) { selection.destroy() } - this.selections = this.selections.slice(0, ranges.length) - for (let i = 0; i < ranges.length; i++) { - const range = ranges[i] - if (this.selections[i]) { - this.selections[i].setBufferRange(range) - } else { - const newSelection = this.editor.addSelectionForBufferRange(range) - this.subscriptions.add(newSelection.cursor.onDidChangePosition(event => this.cursorMoved(event))) - this.subscriptions.add(newSelection.cursor.onDidDestroy(() => this.cursorDestroyed())) - this.selections.push(newSelection) - } - } - markerSelected = true - } - - this.settingTabStop = false - // If this snippet has at least one transform, we need to observe changes - // made to the editor so that we can update the transformed tab stops. - if (this.hasTransforms) { this.snippets.observeEditor(this.editor) } - - return markerSelected - } - - goToEndOfLastTabStop () { - if (this.tabStopMarkers.length === 0) { return } - const items = this.tabStopMarkers[this.tabStopMarkers.length - 1] - if (items.length === 0) { return } - const {marker: lastMarker} = items[items.length - 1] - if (lastMarker.isDestroyed()) { - return false - } else { - this.editor.setCursorBufferPosition(lastMarker.getEndBufferPosition()) - return true - } - } - - destroy () { - this.subscriptions.dispose() - this.getMarkerLayer(this.editor).clear() - this.tabStopMarkers = [] - this.snippets.stopObservingEditor(this.editor) - this.snippets.clearExpansions(this.editor) - } - - getMarkerLayer () { - return this.snippets.findOrCreateMarkerLayer(this.editor) - } - - restore (editor) { - this.editor = editor - this.snippets.addExpansion(this.editor, this) - } -} diff --git a/lib/snippet-history-provider.js b/lib/snippet-history-provider.js deleted file mode 100644 index b1b3e57c..00000000 --- a/lib/snippet-history-provider.js +++ /dev/null @@ -1,27 +0,0 @@ -function wrap (manager, callbacks) { - let klass = new SnippetHistoryProvider(manager) - return new Proxy(manager, { - get (target, name) { - if (name in callbacks) { - callbacks[name]() - } - return name in klass ? klass[name] : target[name] - } - }) -} - -class SnippetHistoryProvider { - constructor (manager) { - this.manager = manager - } - - undo (...args) { - return this.manager.undo(...args) - } - - redo (...args) { - return this.manager.redo(...args) - } -} - -module.exports = wrap diff --git a/lib/snippet.js b/lib/snippet.js deleted file mode 100644 index fcdfed90..00000000 --- a/lib/snippet.js +++ /dev/null @@ -1,56 +0,0 @@ -const {Range} = require('atom') -const TabStopList = require('./tab-stop-list') - -module.exports = class Snippet { - constructor({name, prefix, bodyText, description, descriptionMoreURL, rightLabelHTML, leftLabel, leftLabelHTML, bodyTree}) { - this.name = name - this.prefix = prefix - this.bodyText = bodyText - this.description = description - this.descriptionMoreURL = descriptionMoreURL - this.rightLabelHTML = rightLabelHTML - this.leftLabel = leftLabel - this.leftLabelHTML = leftLabelHTML - this.tabStopList = new TabStopList(this) - this.body = this.extractTabStops(bodyTree) - } - - extractTabStops (bodyTree) { - const bodyText = [] - let row = 0 - let column = 0 - - // recursive helper function; mutates vars above - let extractTabStops = bodyTree => { - for (const segment of bodyTree) { - if (segment.index != null) { - let {index, content, substitution} = segment - if (index === 0) { index = Infinity; } - const start = [row, column] - extractTabStops(content) - const range = new Range(start, [row, column]) - const tabStop = this.tabStopList.findOrCreate({ - index, - snippet: this - }) - tabStop.addInsertion({ range, substitution }) - } else if (typeof segment === 'string') { - bodyText.push(segment) - var segmentLines = segment.split('\n') - column += segmentLines.shift().length - let nextLine - while ((nextLine = segmentLines.shift()) != null) { - row += 1 - column = nextLine.length - } - } - } - } - - extractTabStops(bodyTree) - this.lineCount = row + 1 - this.insertions = this.tabStopList.getInsertions() - - return bodyText.join('') - } -} diff --git a/lib/snippets-available.js b/lib/snippets-available.js deleted file mode 100644 index d244cb16..00000000 --- a/lib/snippets-available.js +++ /dev/null @@ -1,84 +0,0 @@ -/** @babel */ - -import _ from 'underscore-plus' -import SelectListView from 'atom-select-list' - -export default class SnippetsAvailable { - constructor (snippets) { - this.panel = null - this.snippets = snippets - this.selectListView = new SelectListView({ - items: [], - filterKeyForItem: (snippet) => snippet.searchText, - elementForItem: (snippet) => { - const li = document.createElement('li') - li.classList.add('two-lines') - - const primaryLine = document.createElement('div') - primaryLine.classList.add('primary-line') - primaryLine.textContent = snippet.prefix - li.appendChild(primaryLine) - - const secondaryLine = document.createElement('div') - secondaryLine.classList.add('secondary-line') - secondaryLine.textContent = snippet.name - li.appendChild(secondaryLine) - - return li - }, - didConfirmSelection: (snippet) => { - for (const cursor of this.editor.getCursors()) { - this.snippets.insert(snippet.bodyText, this.editor, cursor) - } - this.cancel() - }, - didConfirmEmptySelection: () => { - this.cancel() - }, - didCancelSelection: () => { - this.cancel() - } - }) - this.selectListView.element.classList.add('available-snippets') - this.element = this.selectListView.element - } - - async toggle (editor) { - this.editor = editor - if (this.panel != null) { - this.cancel() - } else { - this.selectListView.reset() - await this.populate() - this.attach() - } - } - - cancel () { - this.editor = null - - if (this.panel != null) { - this.panel.destroy() - this.panel = null - } - - if (this.previouslyFocusedElement) { - this.previouslyFocusedElement.focus() - this.previouslyFocusedElement = null - } - } - - populate () { - const snippets = Object.values(this.snippets.getSnippets(this.editor)) - for (let snippet of snippets) { - snippet.searchText = _.compact([snippet.prefix, snippet.name]).join(' ') - } - return this.selectListView.update({items: snippets}) - } - - attach () { - this.previouslyFocusedElement = document.activeElement - this.panel = atom.workspace.addModalPanel({item: this}) - this.selectListView.focus() - } -} diff --git a/lib/snippets.js b/lib/snippets.js deleted file mode 100644 index 8e67ec0b..00000000 --- a/lib/snippets.js +++ /dev/null @@ -1,666 +0,0 @@ -const path = require('path') -const {Emitter, Disposable, CompositeDisposable, File} = require('atom') -const _ = require('underscore-plus') -const async = require('async') -const CSON = require('season') -const fs = require('fs-plus') -const ScopedPropertyStore = require('scoped-property-store') - -const Snippet = require('./snippet') -const SnippetExpansion = require('./snippet-expansion') -const EditorStore = require('./editor-store') -const {getPackageRoot} = require('./helpers') - -module.exports = { - activate () { - this.loaded = false - this.userSnippetsPath = null - this.snippetIdCounter = 0 - this.snippetsByPackage = new Map - this.parsedSnippetsById = new Map - this.editorMarkerLayers = new WeakMap - - this.scopedPropertyStore = new ScopedPropertyStore - // The above ScopedPropertyStore will store the main registry of snippets. - // But we need a separate ScopedPropertyStore for the snippets that come - // from disabled packages. They're isolated so that they're not considered - // as candidates when the user expands a prefix, but we still need the data - // around so that the snippets provided by those packages can be shown in - // the settings view. - this.disabledSnippetsScopedPropertyStore = new ScopedPropertyStore - - this.subscriptions = new CompositeDisposable - this.subscriptions.add(atom.workspace.addOpener(uri => { - if (uri === 'atom://.atom/snippets') { - return atom.workspace.openTextFile(this.getUserSnippetsPath()) - } - })) - - this.loadAll() - this.watchUserSnippets(watchDisposable => { - this.subscriptions.add(watchDisposable) - }) - - this.subscriptions.add(atom.config.onDidChange('core.packagesWithSnippetsDisabled', ({newValue, oldValue}) => { - this.handleDisabledPackagesDidChange(newValue, oldValue) - })) - - const snippets = this - - this.subscriptions.add(atom.commands.add('atom-text-editor', { - 'snippets:expand'(event) { - const editor = this.getModel() - if (snippets.snippetToExpandUnderCursor(editor)) { - snippets.clearExpansions(editor) - snippets.expandSnippetsUnderCursors(editor) - } else { - event.abortKeyBinding() - } - }, - - 'snippets:next-tab-stop'(event) { - const editor = this.getModel() - if (!snippets.goToNextTabStop(editor)) { event.abortKeyBinding() } - }, - - 'snippets:previous-tab-stop'(event) { - const editor = this.getModel() - if (!snippets.goToPreviousTabStop(editor)) { event.abortKeyBinding() } - }, - - 'snippets:available'(event) { - const editor = this.getModel() - const SnippetsAvailable = require('./snippets-available') - if (snippets.availableSnippetsView == null) { snippets.availableSnippetsView = new SnippetsAvailable(snippets) } - snippets.availableSnippetsView.toggle(editor) - } - })) - }, - - deactivate () { - if (this.emitter != null) { - this.emitter.dispose() - } - this.emitter = null - this.editorSnippetExpansions = null - atom.config.transact(() => this.subscriptions.dispose()) - }, - - getUserSnippetsPath () { - if (this.userSnippetsPath != null) { return this.userSnippetsPath } - - this.userSnippetsPath = CSON.resolve(path.join(atom.getConfigDirPath(), 'snippets')) - if (this.userSnippetsPath == null) { this.userSnippetsPath = path.join(atom.getConfigDirPath(), 'snippets.cson') } - return this.userSnippetsPath - }, - - loadAll () { - this.loadBundledSnippets(bundledSnippets => { - this.loadPackageSnippets(packageSnippets => { - this.loadUserSnippets(userSnippets => { - atom.config.transact(() => { - for (const snippetSet of [bundledSnippets, packageSnippets, userSnippets]) { - for (const filepath in snippetSet) { - const snippetsBySelector = snippetSet[filepath] - this.add(filepath, snippetsBySelector) - } - } - }) - this.doneLoading() - }) - }) - }) - }, - - loadBundledSnippets (callback) { - const bundledSnippetsPath = CSON.resolve(path.join(getPackageRoot(), 'lib', 'snippets')) - this.loadSnippetsFile(bundledSnippetsPath, snippets => { - const snippetsByPath = {} - snippetsByPath[bundledSnippetsPath] = snippets - callback(snippetsByPath) - }) - }, - - loadUserSnippets (callback) { - const userSnippetsPath = this.getUserSnippetsPath() - fs.stat(userSnippetsPath, (error, stat) => { - if (stat != null && stat.isFile()) { - this.loadSnippetsFile(userSnippetsPath, snippets => { - const result = {} - result[userSnippetsPath] = snippets - callback(result) - }) - } else { - callback({}) - } - }) - }, - - watchUserSnippets (callback) { - const userSnippetsPath = this.getUserSnippetsPath() - fs.stat(userSnippetsPath, (error, stat) => { - if (stat != null && stat.isFile()) { - const userSnippetsFileDisposable = new CompositeDisposable() - const userSnippetsFile = new File(userSnippetsPath) - try { - userSnippetsFileDisposable.add(userSnippetsFile.onDidChange(() => this.handleUserSnippetsDidChange())) - userSnippetsFileDisposable.add(userSnippetsFile.onDidDelete(() => this.handleUserSnippetsDidChange())) - userSnippetsFileDisposable.add(userSnippetsFile.onDidRename(() => this.handleUserSnippetsDidChange())) - } catch (e) { - const message = `\ - Unable to watch path: \`snippets.cson\`. Make sure you have permissions - to the \`~/.atom\` directory and \`${userSnippetsPath}\`. - - On linux there are currently problems with watch sizes. See - [this document][watches] for more info. - [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path\ - ` - atom.notifications.addError(message, {dismissable: true}) - } - - callback(userSnippetsFileDisposable) - } else { - callback(new Disposable()) - } - }) - }, - - // Called when a user's snippets file is changed, deleted, or moved so that we - // can immediately re-process the snippets it contains. - handleUserSnippetsDidChange () { - const userSnippetsPath = this.getUserSnippetsPath() - atom.config.transact(() => { - this.clearSnippetsForPath(userSnippetsPath) - this.loadSnippetsFile(userSnippetsPath, result => { - this.add(userSnippetsPath, result) - }) - }) - }, - - // Called when the "Enable" checkbox is checked/unchecked in the Snippets - // section of a package's settings view. - handleDisabledPackagesDidChange (newDisabledPackages = [], oldDisabledPackages = []) { - const packagesToAdd = [] - const packagesToRemove = [] - for (const p of oldDisabledPackages) { - if (!newDisabledPackages.includes(p)) { packagesToAdd.push(p) } - } - - for (const p of newDisabledPackages) { - if (!oldDisabledPackages.includes(p)) { packagesToRemove.push(p) } - } - - atom.config.transact(() => { - for (const p of packagesToRemove) { this.removeSnippetsForPackage(p) } - for (const p of packagesToAdd) { this.addSnippetsForPackage(p) } - }) - }, - - addSnippetsForPackage (packageName) { - const snippetSet = this.snippetsByPackage.get(packageName) - for (const filePath in snippetSet) { - const snippetsBySelector = snippetSet[filePath] - this.add(filePath, snippetsBySelector) - } - }, - - removeSnippetsForPackage (packageName) { - const snippetSet = this.snippetsByPackage.get(packageName) - // Copy these snippets to the "quarantined" ScopedPropertyStore so that they - // remain present in the list of unparsed snippets reported to the settings - // view. - this.addSnippetsInDisabledPackage(snippetSet) - for (const filePath in snippetSet) { - this.clearSnippetsForPath(filePath) - } - }, - - loadPackageSnippets (callback) { - const disabledPackageNames = atom.config.get('core.packagesWithSnippetsDisabled') || [] - const packages = atom.packages.getLoadedPackages().sort((pack, _) => { - return /\/node_modules\//.test(pack.path) ? -1 : 1 - }) - - const snippetsDirPaths = [] - for (const pack of packages) { - snippetsDirPaths.push(path.join(pack.path, 'snippets')) - } - - async.map(snippetsDirPaths, this.loadSnippetsDirectory.bind(this), (error, results) => { - const zipped = [] - for (const key in results) { - zipped.push({result: results[key], pack: packages[key]}) - } - - const enabledPackages = [] - for (const o of zipped) { - // Skip packages that contain no snippets. - if (Object.keys(o.result).length === 0) { continue } - // Keep track of which snippets come from which packages so we can - // unload them selectively later. All packages get put into this map, - // even disabled packages, because we need to know which snippets to add - // if those packages are enabled again. - this.snippetsByPackage.set(o.pack.name, o.result) - if (disabledPackageNames.includes(o.pack.name)) { - // Since disabled packages' snippets won't get added to the main - // ScopedPropertyStore, we'll keep track of them in a separate - // ScopedPropertyStore so that they can still be represented in the - // settings view. - this.addSnippetsInDisabledPackage(o.result) - } else { - enabledPackages.push(o.result) - } - } - - callback(_.extend({}, ...enabledPackages)) - }) - }, - - doneLoading () { - this.loaded = true - this.getEmitter().emit('did-load-snippets') - }, - - onDidLoadSnippets (callback) { - this.getEmitter().on('did-load-snippets', callback) - }, - - getEmitter () { - if (this.emitter == null) { - this.emitter = new Emitter - } - return this.emitter - }, - - loadSnippetsDirectory (snippetsDirPath, callback) { - fs.isDirectory(snippetsDirPath, isDirectory => { - if (!isDirectory) { return callback(null, {}) } - - fs.readdir(snippetsDirPath, (error, entries) => { - if (error) { - console.warn(`Error reading snippets directory ${snippetsDirPath}`, error) - return callback(null, {}) - } - - async.map( - entries, - (entry, done) => { - const filePath = path.join(snippetsDirPath, entry) - this.loadSnippetsFile(filePath, snippets => done(null, {filePath, snippets})) - }, - (error, results) => { - const snippetsByPath = {} - for (const {filePath, snippets} of results) { - snippetsByPath[filePath] = snippets - } - callback(null, snippetsByPath) - }) - }) - }) - }, - - loadSnippetsFile (filePath, callback) { - if (!CSON.isObjectPath(filePath)) { return callback({}) } - CSON.readFile(filePath, {allowDuplicateKeys: false}, (error, object = {}) => { - if (error != null) { - console.warn(`Error reading snippets file '${filePath}': ${error.stack != null ? error.stack : error}`) - atom.notifications.addError(`Failed to load snippets from '${filePath}'`, {detail: error.message, dismissable: true}) - } - callback(object) - }) - }, - - add (filePath, snippetsBySelector, isDisabled = false) { - for (const selector in snippetsBySelector) { - const snippetsByName = snippetsBySelector[selector] - const unparsedSnippetsByPrefix = {} - for (const name in snippetsByName) { - const attributes = snippetsByName[name] - const {prefix, body} = attributes - attributes.name = name - attributes.id = this.snippetIdCounter++ - if (typeof body === 'string') { - unparsedSnippetsByPrefix[prefix] = attributes - } else if (body == null) { - unparsedSnippetsByPrefix[prefix] = null - } - } - - this.storeUnparsedSnippets(unparsedSnippetsByPrefix, filePath, selector, isDisabled) - } - }, - - addSnippetsInDisabledPackage (bundle) { - for (const filePath in bundle) { - const snippetsBySelector = bundle[filePath] - this.add(filePath, snippetsBySelector, true) - } - }, - - getScopeChain (object) { - let scopesArray = object - if (object && object.getScopesArray) { - scopesArray = object.getScopesArray() - } - - return scopesArray - .map(scope => scope[0] === '.' ? scope : `.${scope}`) - .join(' ') - }, - - storeUnparsedSnippets (value, path, selector, isDisabled = false) { - // The `isDisabled` flag determines which scoped property store we'll use. - // Active snippets get put into one and inactive snippets get put into - // another. Only the first one gets consulted when we look up a snippet - // prefix for expansion, but both stores have their contents exported when - // the settings view asks for all available snippets. - const unparsedSnippets = {} - unparsedSnippets[selector] = {"snippets": value} - const store = isDisabled ? this.disabledSnippetsScopedPropertyStore : this.scopedPropertyStore - store.addProperties(path, unparsedSnippets, {priority: this.priorityForSource(path)}) - }, - - clearSnippetsForPath (path) { - for (const scopeSelector in this.scopedPropertyStore.propertiesForSource(path)) { - const object = this.scopedPropertyStore.propertiesForSourceAndSelector(path, scopeSelector) - for (const prefix in object) { - const attributes = object[prefix] - this.parsedSnippetsById.delete(attributes.id) - } - - this.scopedPropertyStore.removePropertiesForSourceAndSelector(path, scopeSelector) - } - }, - - parsedSnippetsForScopes (scopeDescriptor) { - let unparsedLegacySnippetsByPrefix - - const unparsedSnippetsByPrefix = this.scopedPropertyStore.getPropertyValue( - this.getScopeChain(scopeDescriptor), - "snippets" - ) - - const legacyScopeDescriptor = atom.config.getLegacyScopeDescriptorForNewScopeDescriptor - ? atom.config.getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor) - : undefined - - if (legacyScopeDescriptor) { - unparsedLegacySnippetsByPrefix = this.scopedPropertyStore.getPropertyValue( - this.getScopeChain(legacyScopeDescriptor), - "snippets" - ) - } - - const snippets = {} - - if (unparsedSnippetsByPrefix) { - for (const prefix in unparsedSnippetsByPrefix) { - const attributes = unparsedSnippetsByPrefix[prefix] - if (typeof (attributes != null ? attributes.body : undefined) !== 'string') { continue } - snippets[prefix] = this.getParsedSnippet(attributes) - } - } - - if (unparsedLegacySnippetsByPrefix) { - for (const prefix in unparsedLegacySnippetsByPrefix) { - const attributes = unparsedLegacySnippetsByPrefix[prefix] - if (snippets[prefix]) { continue } - if (typeof (attributes != null ? attributes.body : undefined) !== 'string') { continue } - snippets[prefix] = this.getParsedSnippet(attributes) - } - } - - return snippets - }, - - getParsedSnippet (attributes) { - let snippet = this.parsedSnippetsById.get(attributes.id) - if (snippet == null) { - let {id, prefix, name, body, bodyTree, description, descriptionMoreURL, rightLabelHTML, leftLabel, leftLabelHTML} = attributes - if (bodyTree == null) { bodyTree = this.getBodyParser().parse(body) } - snippet = new Snippet({id, name, prefix, bodyTree, description, descriptionMoreURL, rightLabelHTML, leftLabel, leftLabelHTML, bodyText: body}) - this.parsedSnippetsById.set(attributes.id, snippet) - } - return snippet - }, - - priorityForSource (source) { - if (source === this.getUserSnippetsPath()) { - return 1000 - } else { - return 0 - } - }, - - getBodyParser () { - if (this.bodyParser == null) { - this.bodyParser = require('./snippet-body-parser') - } - return this.bodyParser - }, - - // Get an {Object} with these keys: - // * `snippetPrefix`: the possible snippet prefix text preceding the cursor - // * `wordPrefix`: the word preceding the cursor - // - // Returns `null` if the values aren't the same for all cursors - getPrefixText (snippets, editor) { - const wordRegex = this.wordRegexForSnippets(snippets) - - let snippetPrefix = null - let wordPrefix = null - - for (const cursor of editor.getCursors()) { - const position = cursor.getBufferPosition() - - const prefixStart = cursor.getBeginningOfCurrentWordBufferPosition({wordRegex}) - const cursorSnippetPrefix = editor.getTextInRange([prefixStart, position]) - if ((snippetPrefix != null) && (cursorSnippetPrefix !== snippetPrefix)) { return null } - snippetPrefix = cursorSnippetPrefix - - const wordStart = cursor.getBeginningOfCurrentWordBufferPosition() - const cursorWordPrefix = editor.getTextInRange([wordStart, position]) - if ((wordPrefix != null) && (cursorWordPrefix !== wordPrefix)) { return null } - wordPrefix = cursorWordPrefix - } - - return {snippetPrefix, wordPrefix} - }, - - // Get a RegExp of all the characters used in the snippet prefixes - wordRegexForSnippets (snippets) { - const prefixes = {} - - for (const prefix in snippets) { - for (const character of prefix) { prefixes[character] = true } - } - - const prefixCharacters = Object.keys(prefixes).join('') - return new RegExp(`[${_.escapeRegExp(prefixCharacters)}]+`) - }, - - // Get the best match snippet for the given prefix text. This will return - // the longest match where there is no exact match to the prefix text. - snippetForPrefix (snippets, prefix, wordPrefix) { - let longestPrefixMatch = null - - for (const snippetPrefix in snippets) { - const snippet = snippets[snippetPrefix] - if (prefix.endsWith(snippetPrefix) && (wordPrefix.length <= snippetPrefix.length)) { - if ((longestPrefixMatch == null) || (snippetPrefix.length > longestPrefixMatch.prefix.length)) { - longestPrefixMatch = snippet - } - } - } - - return longestPrefixMatch - }, - - getSnippets (editor) { - return this.parsedSnippetsForScopes(editor.getLastCursor().getScopeDescriptor()) - }, - - snippetToExpandUnderCursor (editor) { - if (!editor.getLastSelection().isEmpty()) { return false } - const snippets = this.getSnippets(editor) - if (_.isEmpty(snippets)) { return false } - - const prefixData = this.getPrefixText(snippets, editor) - if (prefixData) { - return this.snippetForPrefix(snippets, prefixData.snippetPrefix, prefixData.wordPrefix) - } - }, - - expandSnippetsUnderCursors (editor) { - const snippet = this.snippetToExpandUnderCursor(editor) - if (!snippet) { return false } - - this.getStore(editor).observeHistory({ - undo: event => { this.onUndoOrRedo(editor, event, true) }, - redo: event => { this.onUndoOrRedo(editor, event, false) } - }) - - this.findOrCreateMarkerLayer(editor) - editor.transact(() => { - const cursors = editor.getCursors() - for (const cursor of cursors) { - const cursorPosition = cursor.getBufferPosition() - const startPoint = cursorPosition.translate([0, -snippet.prefix.length], [0, 0]) - cursor.selection.setBufferRange([startPoint, cursorPosition]) - this.insert(snippet, editor, cursor) - } - }) - return true - }, - - goToNextTabStop (editor) { - let nextTabStopVisited = false - for (const expansion of this.getExpansions(editor)) { - if (expansion && expansion.goToNextTabStop()) { - nextTabStopVisited = true - } - } - return nextTabStopVisited - }, - - goToPreviousTabStop (editor) { - let previousTabStopVisited = false - for (const expansion of this.getExpansions(editor)) { - if (expansion && expansion.goToPreviousTabStop()) { - previousTabStopVisited = true - } - } - return previousTabStopVisited - }, - - getStore (editor) { - return EditorStore.findOrCreate(editor) - }, - - findOrCreateMarkerLayer (editor) { - let layer = this.editorMarkerLayers.get(editor) - if (layer === undefined) { - layer = editor.addMarkerLayer({maintainHistory: true}) - this.editorMarkerLayers.set(editor, layer) - } - return layer - }, - - getExpansions (editor) { - return this.getStore(editor).getExpansions() - }, - - clearExpansions (editor) { - const store = this.getStore(editor) - store.clearExpansions() - // There are no more active instances of this expansion, so we should undo - // the spying we set up on this editor. - store.stopObserving() - store.stopObservingHistory() - }, - - addExpansion (editor, snippetExpansion) { - this.getStore(editor).addExpansion(snippetExpansion) - }, - - textChanged (editor, event) { - const store = this.getStore(editor) - const activeExpansions = store.getExpansions() - - if ((activeExpansions.length === 0) || activeExpansions[0].isIgnoringBufferChanges) { return } - - this.ignoringTextChangesForEditor(editor, () => - editor.transact(() => - activeExpansions.map(expansion => expansion.textChanged(event))) - ) - - // Create a checkpoint here to consolidate all the changes we just made into - // the transaction that prompted them. - this.makeCheckpoint(editor) - }, - - // Perform an action inside the editor without triggering our `textChanged` - // callback. - ignoringTextChangesForEditor (editor, callback) { - this.stopObservingEditor(editor) - callback() - this.observeEditor(editor) - }, - - observeEditor (editor) { - this.getStore(editor).observe(event => this.textChanged(editor, event)) - }, - - stopObservingEditor (editor) { - this.getStore(editor).stopObserving() - }, - - makeCheckpoint (editor) { - this.getStore(editor).makeCheckpoint() - }, - - insert (snippet, editor, cursor) { - if (editor == null) { editor = atom.workspace.getActiveTextEditor() } - if (cursor == null) { cursor = editor.getLastCursor() } - if (typeof snippet === 'string') { - const bodyTree = this.getBodyParser().parse(snippet) - snippet = new Snippet({name: '__anonymous', prefix: '', bodyTree, bodyText: snippet}) - } - return new SnippetExpansion(snippet, editor, cursor, this) - }, - - getUnparsedSnippets () { - const results = [] - const iterate = sets => { - for (const item of sets) { - const newItem = _.deepClone(item) - // The atom-slick library has already parsed the `selector` property, so - // it's an AST here instead of a string. The object has a `toString` - // method that turns it back into a string. That custom behavior won't - // be preserved in the deep clone of the object, so we have to handle it - // separately. - newItem.selectorString = item.selector.toString() - results.push(newItem) - } - } - - iterate(this.scopedPropertyStore.propertySets) - iterate(this.disabledSnippetsScopedPropertyStore.propertySets) - return results - }, - - provideSnippets () { - return { - bundledSnippetsLoaded: () => this.loaded, - insertSnippet: this.insert.bind(this), - snippetsForScopes: this.parsedSnippetsForScopes.bind(this), - getUnparsedSnippets: this.getUnparsedSnippets.bind(this), - getUserSnippetsPath: this.getUserSnippetsPath.bind(this) - } - }, - - onUndoOrRedo (editor, isUndo) { - const activeExpansions = this.getExpansions(editor) - activeExpansions.forEach(expansion => expansion.onUndoOrRedo(isUndo)) - } -} diff --git a/lib/tab-stop-list.js b/lib/tab-stop-list.js deleted file mode 100644 index 0d3bd010..00000000 --- a/lib/tab-stop-list.js +++ /dev/null @@ -1,48 +0,0 @@ -const TabStop = require('./tab-stop') - -class TabStopList { - constructor (snippet) { - this.snippet = snippet - this.list = {} - } - - get length () { - return Object.keys(this.list).length - } - - get hasEndStop () { - return !!this.list[Infinity] - } - - findOrCreate ({ index, snippet }) { - if (!this.list[index]) { - this.list[index] = new TabStop({ index, snippet }) - } - return this.list[index] - } - - forEachIndex (iterator) { - let indices = Object.keys(this.list).sort((a1, a2) => a1 - a2) - indices.forEach(iterator) - } - - getInsertions () { - let results = [] - this.forEachIndex(index => { - results.push(...this.list[index].insertions) - }) - return results - } - - toArray () { - let results = [] - this.forEachIndex(index => { - let tabStop = this.list[index] - if (!tabStop.isValid()) return - results.push(tabStop) - }) - return results - } -} - -module.exports = TabStopList diff --git a/lib/tab-stop.js b/lib/tab-stop.js deleted file mode 100644 index 61a423e4..00000000 --- a/lib/tab-stop.js +++ /dev/null @@ -1,61 +0,0 @@ -const {Range} = require('atom') -const Insertion = require('./insertion') - -// A tab stop: -// * belongs to a snippet -// * has an index (one tab stop per index) -// * has multiple Insertions -class TabStop { - constructor ({ snippet, index, insertions }) { - this.insertions = insertions || [] - Object.assign(this, { snippet, index }) - } - - isValid () { - let any = this.insertions.some(insertion => insertion.isTransformation()) - if (!any) return true - let all = this.insertions.every(insertion => insertion.isTransformation()) - // If there are any transforming insertions, there must be at least one - // non-transforming insertion to act as the primary. - return !all - } - - addInsertion ({ range, substitution }) { - let insertion = new Insertion({ range, substitution }) - let insertions = this.insertions - insertions.push(insertion) - insertions = insertions.sort((i1, i2) => { - return i1.range.start.compare(i2.range.start) - }) - let initial = insertions.find(insertion => !insertion.isTransformation()) - if (initial) { - insertions.splice(insertions.indexOf(initial), 1) - insertions.unshift(initial) - } - this.insertions = insertions - } - - copyWithIndent (indent) { - let { snippet, index, insertions } = this - let newInsertions = insertions.map(insertion => { - let { range, substitution } = insertion - let newRange = Range.fromObject(range, true) - if (newRange.start.row) { - newRange.start.column += indent.length - newRange.end.column += indent.length - } - return new Insertion({ - range: newRange, - substitution - }) - }) - - return new TabStop({ - snippet, - index, - insertions: newInsertions - }) - } -} - -module.exports = TabStop diff --git a/menus/snippets.cson b/menus/snippets.cson deleted file mode 100644 index 6557d2a5..00000000 --- a/menus/snippets.cson +++ /dev/null @@ -1,12 +0,0 @@ -'menu': [ - 'label': 'Packages' - 'submenu': [ - 'label': 'Snippets' - 'submenu': [ - { 'label': 'Expand', 'command': 'snippets:show' } - { 'label': 'Next Stop', 'command': 'snippets:next-tab-stop' } - { 'label': 'Previous Stop', 'command': 'snippets:previous-tab-stop' } - { 'label': 'Available', 'command': 'snippets:available' } - ] - ] -] diff --git a/package-lock.json b/package-lock.json index c0850503..cd9448e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,188 @@ { "name": "snippets", - "version": "1.6.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/eslint-parser": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.13.14.tgz", + "integrity": "sha512-I0HweR36D73Ibn/FfrRDMKlMqJHFwidIUgYdMpH+aXYuQC+waq59YaJ6t9e9N36axJ82v1jR041wwqDrDXEwRA==", + "dev": true, + "requires": { + "eslint-scope": "^5.1.0", + "eslint-visitor-keys": "^1.3.0", + "semver": "^6.3.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", + "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + } + } + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true }, "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "atom-select-list": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/atom-select-list/-/atom-select-list-0.7.2.tgz", - "integrity": "sha512-a707OB1DhLGjzqtFrtMQKH7BBxFuCh8UBoUWxgFOrLrSwVh3g+/TlVPVDOz12+U0mDu3mIrnYLqQyhywQOTxhw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/atom-select-list/-/atom-select-list-0.8.0.tgz", + "integrity": "sha512-LZBLl2Mn0ov/CfUV+INrfENQVVdfdXvdB4MGvmvM28Rsm/ViVAuVMjNotvZKVCo5Jm53s/Ixd8K1deQV2WHcxA==", "requires": { - "etch": "^0.12.6", + "etch": "^0.14.0", "fuzzaldrin": "^2.1.0" } }, @@ -29,24 +192,51 @@ "integrity": "sha1-/w2+Fb4sTtomi50w124lF+C308o=" }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -55,6 +245,39 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "code-point-at": { @@ -67,43 +290,43 @@ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==" }, - "coffeelint": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/coffeelint/-/coffeelint-1.16.0.tgz", - "integrity": "sha1-g9jtHa/eOmd95E57ihi+YHdh5tg=", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "coffee-script": "~1.11.0", - "glob": "^7.0.6", - "ignore": "^3.0.9", - "optimist": "^0.6.1", - "resolve": "^0.6.3", - "strip-json-comments": "^1.0.2" - }, - "dependencies": { - "coffee-script": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz", - "integrity": "sha1-vxxHrWREOg2V0S3ysUfMCk2q1uk=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - } + "color-name": "1.1.3" } }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "cson-parser": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.3.5.tgz", @@ -120,11 +343,44 @@ "es5-ext": "~0.10.2" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "emissary": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/emissary/-/emissary-1.3.3.tgz", @@ -136,40 +392,101 @@ "underscore-plus": "1.x" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { - "version": "0.10.30", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz", - "integrity": "sha1-cUGhaDZpfbq/qq7uQUlc4p9SyTk=", + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "requires": { - "es6-iterator": "2", - "es6-symbol": "~3.1" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" }, "dependencies": { "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "^0.10.50", + "type": "^1.0.1" } }, "es6-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", - "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1", - "es5-ext": "^0.10.14", - "es6-symbol": "^3.1" + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "^1.0.1", + "ext": "^1.1.2" } } } @@ -204,100 +521,819 @@ "es6-symbol": "~2.0.1" } }, - "etch": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/etch/-/etch-0.12.8.tgz", - "integrity": "sha512-dFLRe4wLroVtwzyy1vGlE3BSDZHiL0kZME5XgNGzZIULcYTvVno8vbiIleAesoKJmwWaxDTzG+4eppg2zk14JQ==" - }, - "event-kit": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-1.5.0.tgz", - "integrity": "sha1-Ek72qtgyjcsmtxxHWQtbjmPrxIc=", - "requires": { - "grim": "^1.2.1" - } + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, - "fs-plus": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.1.tgz", - "integrity": "sha1-VMFpxA4ohKZtNSeA0Y3TH5HToQ0=", + "eslint": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.13.0.tgz", + "integrity": "sha512-uCORMuOO8tUzJmsdRtrvcGq5qposf7Rw0LwkTJkoDbOycVQtQjmnhZSuLQnozLE4TmAzlMVV45eCHmQ1OpDKUQ==", + "dev": true, "requires": { - "async": "^1.5.2", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2", - "underscore-plus": "1.x" + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.2.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "eslint-config-standard": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.2.tgz", + "integrity": "sha512-fx3f1rJDsl9bY7qzyX8SAtP8GBSk6MfXFaTfaGgk12aAYW4gJSyRm7dM790L6cbXv63fvjY4XeSzXnb4WM+SKw==", + "dev": true }, - "fuzzaldrin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", - "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=" + "eslint-config-standard-jsx": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz", + "integrity": "sha512-hLeA2f5e06W1xyr/93/QJulN/rLbUVUmqTlexv9PRKHFwEC9ffJcH2LvJhMoEqYQBEYafedgGZXH2W8NUpt5lA==", + "dev": true }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, - "grim": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/grim/-/grim-1.5.0.tgz", - "integrity": "sha1-sysI71Z88YUvgXWe2caLDXE5ajI=", + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, "requires": { - "emissary": "^1.2.0" + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, - "ignore": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", - "integrity": "sha1-xOcVRV9gc6jX5drnLS/J1xZj26Y=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "eslint-plugin-import": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" - } + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", + "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.18.1", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etch": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/etch/-/etch-0.14.1.tgz", + "integrity": "sha512-+IwqSDBhaQFMUHJu4L/ir0dhDoW5IIihg4Z9lzsIxxne8V0PlSg0gnk2STaKWjGJQnDR4cxpA+a/dORX9kycTA==" + }, + "event-kit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-1.5.0.tgz", + "integrity": "sha1-Ek72qtgyjcsmtxxHWQtbjmPrxIc=", + "requires": { + "grim": "^1.2.1" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs-plus": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz", + "integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==", + "requires": { + "async": "^1.5.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2", + "underscore-plus": "1.x" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "fuzzaldrin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", + "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "grim": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/grim/-/grim-1.5.0.tgz", + "integrity": "sha1-sysI71Z88YUvgXWe2caLDXE5ajI=", + "requires": { + "emissary": "^1.2.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", + "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "dev": true + }, + "is-boolean-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", + "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsx-ast-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + } }, "key-path-helpers": { "version": "0.1.0", @@ -312,72 +1348,439 @@ "invert-kv": "^1.0.0" } }, - "loophole": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loophole/-/loophole-1.1.0.tgz", - "integrity": "sha1-N5Sf6kU7YlasxyXDIM4MWn9wor0=" + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mixto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz", + "integrity": "sha1-wyDvYbUvKJj1IuF9i7xtUG2EJbY=" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mock-fs": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.13.0.tgz", + "integrity": "sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", + "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", + "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, - "mixto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz", - "integrity": "sha1-wyDvYbUvKJj1IuF9i7xtUG2EJbY=" + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, "requires": { - "minimist": "0.0.8" + "pify": "^2.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "pegjs": { + "version": "0.11.0-master.b7b87ea", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.11.0-master.b7b87ea.tgz", + "integrity": "sha512-fwjzNiYHRUEUe/86Aaslb/ocbbsAupOcsJz+dlPYtgp3feCDRQOLChHO924XGh7fzSJBTdFCQTzmSOQaWjCTew==", + "dev": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, "requires": { - "wrappy": "1" + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } } }, - "os-locale": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, "requires": { - "lcid": "^1.0.0" + "find-up": "^2.1.0" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, - "pegjs": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.8.0.tgz", - "integrity": "sha1-l28GfaE+XFsVAcAXklZoolOBFWE=" + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } }, "property-accessors": { "version": "1.1.3", @@ -388,18 +1791,78 @@ "mixto": "1.x" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "resolve": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", - "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "scoped-property-store": { @@ -424,57 +1887,339 @@ "yargs": "^3.23.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/standard/-/standard-16.0.3.tgz", + "integrity": "sha512-70F7NH0hSkNXosXRltjSv6KpTAOkUkSfyu3ynyM5dtRUiLtR+yX9EGZ7RKwuGUqCJiX/cnkceVM6HTZ4JpaqDg==", + "dev": true, + "requires": { + "eslint": "~7.13.0", + "eslint-config-standard": "16.0.2", + "eslint-config-standard-jsx": "10.0.0", + "eslint-plugin-import": "~2.22.1", + "eslint-plugin-node": "~11.1.0", + "eslint-plugin-promise": "~4.2.1", + "eslint-plugin-react": "~7.21.5", + "standard-engine": "^14.0.1" + } + }, + "standard-engine": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-14.0.1.tgz", + "integrity": "sha512-7FEzDwmHDOGva7r9ifOzD3BGdTbA7ujJ50afLVdW/tK14zQEptJjbFuUfn50irqdHDcTbNh0DTIoMPynMCXb0Q==", + "dev": true, + "requires": { + "get-stdin": "^8.0.0", + "minimist": "^1.2.5", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" + } + }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz", + "integrity": "sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.3.1", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, "requires": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" }, "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } } } }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" }, "underscore-plus": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.6.tgz", - "integrity": "sha1-ZezeG9xEGjXYnmUP1w3PE65Dmn0=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz", + "integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==", + "requires": { + "underscore": "^1.9.1" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { - "underscore": "~1.6.0" + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" } }, "window-size": { @@ -482,19 +2227,52 @@ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "wrappy": { @@ -502,14 +2280,35 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "yargs": { "version": "3.32.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "requires": { "camelcase": "^2.0.1", @@ -519,6 +2318,39 @@ "string-width": "^1.0.1", "window-size": "^0.1.4", "y18n": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } } } } diff --git a/package.json b/package.json index 7ced0272..1b1cbd80 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,51 @@ { "name": "snippets", - "version": "1.6.0", - "main": "./lib/snippets", + "version": "2.0.0", "description": "Expand snippets matching the current prefix with `tab`.", "repository": "https://github.com/atom/snippets", "license": "MIT", - "engines": { - "atom": "*" + "main": "./snippets.js", + "scripts": { + "generate": "pegjs -c ./parser/config.js", + "lint": "standard", + "lint:fix": "npm run lint -- --fix", + "test": "atom --test specs" }, "dependencies": { - "async": "~0.2.6", - "atom-select-list": "^0.7.0", - "fs-plus": "^3.0.0", - "loophole": "^1", - "pegjs": "~0.8.0", + "atom-select-list": "^0.8.0", "scoped-property-store": "^0.17.0", - "season": "^6.0.2", - "temp": "~0.8.0", - "underscore-plus": "^1.0.0" + "season": "^6.0.2" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.13.14", + "mock-fs": "^4.13.0", + "pegjs": "0.11.0-master.b7b87ea", + "standard": "^16.0.3" + }, + "engines": { + "atom": ">=1.56.0", + "node": ">=12.14.1" }, "providedServices": { "snippets": { "description": "Snippets are text shortcuts that can be expanded to their definition.", "versions": { - "0.1.0": "provideSnippets" + "1.0.0": "snippets" } } }, - "devDependencies": { - "coffeelint": "^1.9.7" + "standard": { + "parser": "@babel/eslint-parser", + "env": { + "jasmine": true, + "node": true + }, + "ignore": [ + "spec/fixtures/" + ], + "globals": [ + "waitsForPromise", + "atom" + ] } } diff --git a/parser/config.js b/parser/config.js new file mode 100644 index 00000000..8b62d5f5 --- /dev/null +++ b/parser/config.js @@ -0,0 +1,11 @@ +module.exports = { + input: './parser/snippet-body-parser.pegjs', + output: './parser/snippet-body-parser.js', + dependencies: { + Expression: '../expression/expression', + Snippet: '../expression/snippet', + Choice: '../expression/choice', + Placeholder: '../expression/placeholder', + Transformation: '../expression/transformation' + } +} diff --git a/parser/snippet-body-parser.js b/parser/snippet-body-parser.js new file mode 100644 index 00000000..ca40945c --- /dev/null +++ b/parser/snippet-body-parser.js @@ -0,0 +1,1584 @@ +// Generated by PEG.js v0.11.0-master.b7b87ea, https://pegjs.org/ + +"use strict"; + +var Expression = require("../expression/expression"); +var Snippet = require("../expression/snippet"); +var Choice = require("../expression/choice"); +var Placeholder = require("../expression/placeholder"); +var Transformation = require("../expression/transformation"); + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + // istanbul ignore next + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } +} + +peg$subclass(peg$SyntaxError, Error); + +peg$SyntaxError.buildMessage = function(expected, found, location) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + }, + + not: function(expectation) { + return "not " + describeExpectation(expectation.expected); + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + + var peg$startRuleFunctions = { Snippet: peg$parseSnippet }; + var peg$startRuleFunction = peg$parseSnippet; + + var peg$c0 = "$"; + var peg$c1 = "${"; + var peg$c2 = "}"; + var peg$c3 = "|"; + var peg$c4 = ","; + var peg$c5 = ":"; + var peg$c6 = "/"; + var peg$c7 = "/upcase"; + var peg$c8 = "/downcase"; + var peg$c9 = "/capitalize"; + var peg$c10 = "+"; + var peg$c11 = "-"; + var peg$c12 = "?"; + var peg$c13 = "\\"; + + var peg$r0 = /^[gimsuy]/; + var peg$r1 = /^[0-9]/; + + var peg$e0 = peg$literalExpectation("$", false); + var peg$e1 = peg$literalExpectation("${", false); + var peg$e2 = peg$literalExpectation("}", false); + var peg$e3 = peg$anyExpectation(); + var peg$e4 = peg$literalExpectation("|", false); + var peg$e5 = peg$literalExpectation(",", false); + var peg$e6 = peg$literalExpectation(":", false); + var peg$e7 = peg$literalExpectation("/", false); + var peg$e8 = peg$classExpectation(["g", "i", "m", "s", "u", "y"], false, false); + var peg$e9 = peg$literalExpectation("/upcase", false); + var peg$e10 = peg$literalExpectation("/downcase", false); + var peg$e11 = peg$literalExpectation("/capitalize", false); + var peg$e12 = peg$literalExpectation("+", false); + var peg$e13 = peg$literalExpectation("-", false); + var peg$e14 = peg$literalExpectation("?", false); + var peg$e15 = peg$classExpectation([["0", "9"]], false, false); + var peg$e16 = peg$literalExpectation("\\", false); + + var peg$f0 = function(body) { return new Snippet(body) }; + var peg$f1 = function(id) { return new Expression(id) }; + var peg$f2 = function(id, content) { return new Placeholder(id, content) }; + var peg$f3 = function(id, transformation) { return new Transformation(id, transformation) }; + var peg$f4 = function(id, choices) { return new Choice(id, choices) }; + var peg$f5 = function(char) { return /[\p{L}\d_]/u.test(char) }; + var peg$f6 = function(first, rest) { return [...rest, first] }; + var peg$f7 = function() { return escape(',|') }; + var peg$f8 = function() { /*{*/ return escape('$}') }; + var peg$f9 = function() { return escape('/') }; + var peg$f10 = function() { return escape('$/') }; + var peg$f11 = function() { return [''] }; + var peg$f12 = function() { /*{*/ return escape('}') }; + var peg$f13 = function() { return toUpper }; + var peg$f14 = function() { return toLower }; + var peg$f15 = function() { return upperFirst }; + var peg$f16 = function(if_) { return [if_, ''] }; + var peg$f17 = function(else_) { return ['', else_] }; + var peg$f18 = function() { return escape(':') }; + var peg$f19 = function(int) { return Number.parseInt(int) }; + var peg$f20 = function(char) { return !escaped(char) }; + var peg$f21 = function(chars) { return chars.join('') }; + var peg$f22 = function() { return escapes.pop() }; + var peg$f23 = function(char) { return escaped(char) }; + + var peg$currPos = 0; + var peg$savedPos = 0; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$expected = []; + var peg$silentFails = 0; + + var peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return [peg$savedPos, peg$currPos]; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + var peg$VALIDFILENAME = typeof options.filename === "string" && options.filename.length > 0; + function peg$computeLocation(startPos, endPos) { + var loc = {}; + + if ( peg$VALIDFILENAME ) loc.filename = options.filename; + + var startPosDetails = peg$computePosDetails(startPos); + loc.start = { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }; + + var endPosDetails = peg$computePosDetails(endPos); + loc.end = { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + }; + + return loc; + } + + function peg$begin() { + peg$expected.push({ pos: peg$currPos, variants: [] }); + } + + function peg$expect(expected) { + var top = peg$expected[peg$expected.length - 1]; + + if (peg$currPos < top.pos) { return; } + + if (peg$currPos > top.pos) { + top.pos = peg$currPos; + top.variants = []; + } + + top.variants.push(expected); + } + + function peg$end(invert) { + var expected = peg$expected.pop(); + var top = peg$expected[peg$expected.length - 1]; + var variants = expected.variants; + + if (top.pos !== expected.pos) { return; } + + if (invert) { + variants = variants.map(function(e) { + return e.type === "not" ? e.expected : { type: "not", expected: e }; + }); + } + + Array.prototype.push.apply(top.variants, variants); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found, location), + expected, + found, + location + ); + } + + function peg$buildError() { + var expected = peg$expected[0]; + var failPos = expected.pos; + + return peg$buildStructuredError( + expected.variants, + failPos < input.length ? input.charAt(failPos) : null, + failPos < input.length + ? peg$computeLocation(failPos, failPos + 1) + : peg$computeLocation(failPos, failPos) + ); + } + + function peg$parseSnippet() { + var s0, s1, s2; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseExpression(); + if (s2 === peg$FAILED) { + s2 = peg$parseString(); + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseExpression(); + if (s2 === peg$FAILED) { + s2 = peg$parseString(); + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f0(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseExpression() { + var s0, s1, s2, s3, s4; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e0); + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 === peg$FAILED) { + s2 = peg$parseVariable(); + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f1(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 === peg$FAILED) { + s2 = peg$parseVariable(); + } + if (s2 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c2; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f1(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 === peg$FAILED) { + s2 = peg$parseVariable(); + } + if (s2 !== peg$FAILED) { + s3 = peg$parsePlaceholder(); + if (s3 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s4 = peg$c2; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseVariable(); + if (s2 !== peg$FAILED) { + s3 = peg$parseTransformation(); + if (s3 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s4 = peg$c2; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f3(s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 !== peg$FAILED) { + s3 = peg$parseChoice(); + if (s3 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s4 = peg$c2; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + } + + return s0; + } + + function peg$parseVariable() { + var s0, s1, s2, s3, s4; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + s1 = []; + s2 = peg$currPos; + rule$expects(peg$e3); + if (input.length > peg$currPos) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s4 = peg$f5(s3); + if (s4) { + s4 = undefined; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s3 = [s3, s4]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$currPos; + rule$expects(peg$e3); + if (input.length > peg$currPos) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s4 = peg$f5(s3); + if (s4) { + s4 = undefined; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s3 = [s3, s4]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s0 = input.substring(s0, peg$currPos); + } else { + s0 = s1; + } + + return s0; + } + + function peg$parseChoice() { + var s0, s1, s2, s3, s4, s5, s6; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e4); + if (input.charCodeAt(peg$currPos) === 124) { + s1 = peg$c3; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseSelection(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$currPos; + rule$expects(peg$e5); + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c4; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$parseSelection(); + if (s6 !== peg$FAILED) { + s4 = s6; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$currPos; + rule$expects(peg$e5); + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c4; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$parseSelection(); + if (s6 !== peg$FAILED) { + s4 = s6; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } + rule$expects(peg$e4); + if (input.charCodeAt(peg$currPos) === 124) { + s4 = peg$c3; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseSelection() { + var s0, s1, s2, s3; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f7(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseString(); + if (s2 !== peg$FAILED) { + s3 = peg$parseEOL(); + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsePlaceholder() { + var s0, s1, s2, s3, s4; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e6); + if (input.charCodeAt(peg$currPos) === 58) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s2 = peg$f8(); + if (s2) { + s2 = undefined; + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseSnippet(); + if (s3 !== peg$FAILED) { + s4 = peg$parseEOL(); + if (s4 !== peg$FAILED) { + s0 = s3; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseTransformation() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e7); + if (input.charCodeAt(peg$currPos) === 47) { + s1 = peg$c6; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseRegExp(); + if (s2 !== peg$FAILED) { + rule$expects(peg$e7); + if (input.charCodeAt(peg$currPos) === 47) { + s3 = peg$c6; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s4 = peg$parseFormat(); + if (s4 !== peg$FAILED) { + rule$expects(peg$e7); + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c6; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$currPos; + s7 = []; + rule$expects(peg$e8); + if (peg$r0.test(input.charAt(peg$currPos))) { + s8 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s8 = peg$FAILED; + } + while (s8 !== peg$FAILED) { + s7.push(s8); + rule$expects(peg$e8); + if (peg$r0.test(input.charAt(peg$currPos))) { + s8 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s8 = peg$FAILED; + } + } + s6 = input.substring(s6, peg$currPos); + s0 = [ s2, s4, s6 ]; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseRegExp() { + var s0, s1, s2, s3; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f9(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseString(); + if (s2 !== peg$FAILED) { + s3 = peg$parseEOL(); + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseFormat() { + var s0, s1, s2, s3; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f10(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseInsert(); + if (s3 === peg$FAILED) { + s3 = peg$parseString(); + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseInsert(); + if (s3 === peg$FAILED) { + s3 = peg$parseString(); + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseEOL(); + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = ''; + peg$savedPos = s0; + s1 = peg$f11(); + s0 = s1; + } + + return s0; + } + + function peg$parseInsert() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e0); + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c2; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e1); + if (input.substr(peg$currPos, 2) === peg$c1) { + s1 = peg$c1; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInt(); + if (s2 !== peg$FAILED) { + rule$expects(peg$e6); + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c5; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s4 = peg$f12(); + if (s4) { + s4 = undefined; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$parseTransform(); + if (s5 !== peg$FAILED) { + s6 = peg$parseEOL(); + if (s6 !== peg$FAILED) { + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 125) { + s7 = peg$c2; + peg$currPos++; + } else { + s7 = peg$FAILED; + } + if (s7 !== peg$FAILED) { + s0 = [ s2, s5 ]; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + + return s0; + } + + function peg$parseTransform() { + var s0, s1, s2, s3, s4, s5, s6; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e9); + if (input.substr(peg$currPos, 7) === peg$c7) { + s1 = peg$c7; + peg$currPos += 7; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f13(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e10); + if (input.substr(peg$currPos, 9) === peg$c8) { + s1 = peg$c8; + peg$currPos += 9; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f14(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e11); + if (input.substr(peg$currPos, 11) === peg$c9) { + s1 = peg$c9; + peg$currPos += 11; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f15(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e12); + if (input.charCodeAt(peg$currPos) === 43) { + s1 = peg$c10; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseString(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f16(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e13); + if (input.charCodeAt(peg$currPos) === 45) { + s1 = peg$c11; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseString(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + rule$expects(peg$e14); + if (input.charCodeAt(peg$currPos) === 63) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s2 = peg$f18(); + if (s2) { + s2 = undefined; + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseString(); + if (s3 !== peg$FAILED) { + s4 = peg$parseEOL(); + if (s4 !== peg$FAILED) { + rule$expects(peg$e6); + if (input.charCodeAt(peg$currPos) === 58) { + s5 = peg$c5; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$parseString(); + if (s6 !== peg$FAILED) { + s0 = [ s3, s6 ]; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + } + } + + return s0; + } + + function peg$parseInt() { + var s0, s1, s2, s3; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + s1 = peg$currPos; + s2 = []; + rule$expects(peg$e15); + if (peg$r1.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + rule$expects(peg$e15); + if (peg$r1.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = input.substring(s1, peg$currPos); + } else { + s1 = s2; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f19(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseString() { + var s0, s1, s2, s3, s4; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + s1 = []; + s2 = peg$currPos; + s3 = peg$parseEscape(); + if (s3 !== peg$FAILED) { + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 === peg$FAILED) { + s2 = peg$currPos; + rule$expects(peg$e3); + if (input.length > peg$currPos) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s4 = peg$f20(s3); + if (s4) { + s4 = undefined; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$currPos; + s3 = peg$parseEscape(); + if (s3 !== peg$FAILED) { + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 === peg$FAILED) { + s2 = peg$currPos; + rule$expects(peg$e3); + if (input.length > peg$currPos) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s4 = peg$f20(s3); + if (s4) { + s4 = undefined; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f21(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseEOL() { + var s0; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + peg$savedPos = peg$currPos; + s0 = peg$f22(); + if (s0) { + s0 = undefined; + } else { + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseEscape() { + var s0, s1, s2, s3; + + var rule$expects = function (expected) { + if (peg$silentFails === 0) peg$expect(expected); + } + + s0 = peg$currPos; + rule$expects(peg$e16); + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c13; + peg$currPos++; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + rule$expects(peg$e3); + if (input.length > peg$currPos) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = peg$currPos; + s3 = peg$f23(s2); + if (s3) { + s3 = undefined; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + + const toUpper = string => string.toLocaleUpperCase() + const toLower = string => string.toLocaleLowerCase() + const upperFirst = string => string.replace(/^\p{CWU}/u, char => toUpper(char)) + + // Handle and allow certain characters to be escaped in certain contexts + const escapes = ['$'] + const escape = iterable => escapes.push(iterable) + const escaped = value => escapes[escapes.length - 1].includes(value) + + + peg$begin(); + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$expect(peg$endExpectation()); + } + + throw peg$buildError(); + } +} + +module.exports = { + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/parser/snippet-body-parser.pegjs b/parser/snippet-body-parser.pegjs new file mode 100644 index 00000000..c6a13d0a --- /dev/null +++ b/parser/snippet-body-parser.pegjs @@ -0,0 +1,58 @@ +{ + const toUpper = string => string.toLocaleUpperCase() + const toLower = string => string.toLocaleLowerCase() + const upperFirst = string => string.replace(/^\p{CWU}/u, char => toUpper(char)) + + // Handle and allow certain characters to be escaped in certain contexts + const escapes = ['$'] + const escape = iterable => escapes.push(iterable) + const escaped = value => escapes[escapes.length - 1].includes(value) +} + +Snippet = body:(Expression / String)+ { return new Snippet(body) } + +Expression + = "$" id:(Tabstop / Variable) { return new Expression(id) } + / "${" id:(Tabstop / Variable) "}" { return new Expression(id) } + / "${" id:(Tabstop / Variable) content:Placeholder "}" { return new Placeholder(id, content) } + / "${" id:Variable transformation:Transformation "}" { return new Transformation(id, transformation) } + / "${" id:Tabstop choices:Choice "}" { return new Choice(id, choices) } + +Tabstop = Int + +Variable = $(char:. & { return /[\p{L}\d_]/u.test(char) })+ + +Choice = "|" first:Selection rest:("," @Selection)* "|" { return [...rest, first] } + +Selection = & { return escape(',|') } @String EOL + +Placeholder = ":" & { /*{*/ return escape('$}') } @Snippet EOL + +Transformation = "/" @RegExp "/" @Format "/" @$[gimsuy]* + +RegExp = & { return escape('/') } @String EOL + +Format + = & { return escape('$/') } @(Insert / String)+ EOL + / "" { return [''] } + +Insert + = '$' @Int + / '${' @Int '}' + / '${' @Int ':' & { /*{*/ return escape('}') } @Transform EOL '}' + +Transform + = '/upcase' { return toUpper } + / '/downcase' { return toLower } + / '/capitalize' { return upperFirst } + / '+' if_:String { return [if_, ''] } + / '-'? else_:String { return ['', else_] } + / '?' & { return escape(':') } @String EOL ':' @String + +Int = int:$[0-9]+ { return Number.parseInt(int) } + +String = chars:(@Escape / @char:. & { return !escaped(char) })+ { return chars.join('') } + +EOL = & { return escapes.pop() } + +Escape = "\\" @char:. & { return escaped(char) } diff --git a/snippets.js b/snippets.js new file mode 100644 index 00000000..baefc9ef --- /dev/null +++ b/snippets.js @@ -0,0 +1,145 @@ +const { CompositeDisposable, File } = require('atom') + +const CSON = require('season') +const path = require('path') + +const { promises: fs } = require('fs') + +const ScopedPropertyStore = require('scoped-property-store') + +const AvailableSnippetsView = require('./available-snippets-view') + +const parser = require('./parser/snippet-body-parser.js') + +// TODO: Convert private arrow functions into methods once atom supports them +module.exports = class Snippets { + static #disposables = new CompositeDisposable() + // This needs to be made available now even if we reconstruct it on activation + // as service objects can potentially access it before that happens + static #snippetsByScopes = new ScopedPropertyStore() + static #snippetsByPackage = new WeakMap() + + static #userSnippetsFile + static #userSnippetsBasename = path.join(atom.getConfigDirPath(), 'snippets') + + static #userSnippetsURI = 'atom://.atom/snippets' + + // TODO: Uncomment once atom supports private methods + // static get #userSnippetsPath () { + // // See #loadUserSnippets + // return this.#userSnippetsFile.getPath() + // } + + static snippets () { + // Consider having a static frozen object and not creating a new one each + // call, as modifying the service object is often a mistake / bad practice + return { + parse: string => parser.parse(string), + // TODO: Drop 'snippets' prefix 'snippets.snippetsByScopes' is too verbose + snippetsByScopes: () => this.#snippetsByScopes, + snippetsByPackage: () => this.#snippetsByPackage, + // Returns the path _currently in use_ + userSnippetsPath: () => this.#userSnippetsFile.getPath() + } + } + + static async activate () { + // As there's no built-in way to be notified when package activation is + // complete, the loading of package snippets has to be started synchronously + // (before our activationPromise resolves) so that service consumers can + // reliably access the generated promises. + const promises = atom.packages.getLoadedPackages().map(pack => + this.#snippetsByPackage.set(pack, this.#loadPackage(pack)).get(pack)) + + // The above also applies to '#userSnippetsFile' and '#userSnippetsPath' + promises.push(this.#loadUserSnippets({ dispose: () => {} })) + + this.#disposables.add( + atom.workspace.addOpener(uri => uri === this.#userSnippetsURI && + atom.workspace.open(this.#userSnippetsFile.getPath())), + atom.packages.onDidLoadPackage(pack => + this.#snippetsByPackage.set(pack, this.#loadPackage(pack))), + atom.config.observe('core.packagesWithSnippetsDisabled', packs => + this.#togglePackages(new Set(packs))), + atom.commands.add('atom-text-editor', 'snippets:available', event => + new AvailableSnippetsView(this.snippets(), event.currentTarget.getModel()))) + + await Promise.all(promises) + } + + static deactivate () { + this.#disposables.dispose() + } + + static #readSnippets = async (filepath) => { + try { + return await new Promise((resolve, reject) => + CSON.readFile(filepath, (error, object) => + error == null ? resolve(object) : reject(error))) + } catch (error) { + atom.notifications.addWarning(`Unable to load snippets from: '${filepath}'`, { + description: 'Make sure you have permissions to access the directory and file.', + detail: error.toString(), + stack: error.stack, + dismissable: true + }) + return {} + } + } + + // Also updates the user snippets file + static #loadUserSnippets = async (oldSnippets, priority = 1) => { + // Remove old user defined snippets + oldSnippets.dispose() + + this.#userSnippetsFile = new File(`${this.#userSnippetsBasename}.json`) + if (!(await this.#userSnippetsFile.exists())) { + this.#userSnippetsFile = new File(`${this.#userSnippetsBasename}.cson`) + } + await this.#userSnippetsFile.create() + const snippets = await this.#readSnippets(this.#userSnippetsFile.getPath()) + + const disposable = new CompositeDisposable( + this.#snippetsByScopes.addProperties(this.#userSnippetsFile.getPath(), snippets, { priority }), + this.#userSnippetsFile.onDidChange(() => this.#loadUserSnippets(disposable)), + this.#userSnippetsFile.onDidDelete(() => this.#loadUserSnippets(disposable)), + this.#userSnippetsFile.onDidRename(() => this.#loadUserSnippets(disposable)), + { dispose: () => this.#disposables.remove(disposable) }) + + this.#disposables.add(disposable) + } + + static #loadPackage = async (pack) => { + const directory = path.join(pack.path, 'snippets') + try { + const files = await fs.readdir(directory) + const snippets = files.map(file => this.#readSnippets(path.join(directory, file))) + // Reduces the snippets into a single object + return Object.assign(...await Promise.all(snippets)) + } catch (error) { + if (error.code !== 'ENOTDIR' && error.code !== 'ENOENT') { + atom.notifications.addError(`Error reading snippets directory ${directory}`, { + description: 'Make sure you have permissions to access the directory.', + detail: error.toString(), + stack: error.stack, + dismissable: true + }) + } + // Path either doesn't exist, or isn't a directory + return {} + } + } + + static #togglePackages = (packs) => { + // Technically we could compute and toggle only the packages that were + // enabled / disabled, but that would result in more complex code and often + // be slower because of how many iterations 'ScopedPropertyStore' would make + // over its own internal data structures. Thus we just reset + this.#snippetsByScopes = new ScopedPropertyStore() + // (Eventually) Reconstruct the whole scoped snippet storage + atom.packages.getLoadedPackages() + .filter(({ name }) => !packs.has(name)) + .forEach(pack => this.#snippetsByPackage.get(pack).then(snippets => + this.#snippetsByScopes.addProperties(pack.path, snippets))) + } +} diff --git a/lib/snippets.cson b/snippets/snippets.cson similarity index 100% rename from lib/snippets.cson rename to snippets/snippets.cson diff --git a/spec/body-parser-spec.js b/spec/body-parser-spec.js index 35492ded..0a4953b1 100644 --- a/spec/body-parser-spec.js +++ b/spec/body-parser-spec.js @@ -1,86 +1,103 @@ -const BodyParser = require('../lib/snippet-body-parser'); +/* eslint no-template-curly-in-string: 0 */ -describe("Snippet Body Parser", () => { - it("breaks a snippet body into lines, with each line containing tab stops at the appropriate position", () => { - const bodyTree = BodyParser.parse(`\ +const fs = require('fs-plus') +const peg = require('pegjs') + +const Snippet = require('../lib/snippet') +const Variable = require('../lib/variable') +const VariableRegistery = require('../lib/variable-registery') + +const grammar = fs.readFileSync(require.resolve('../lib/snippet-body-parser.pegjs'), 'utf8') + +describe('Snippet Body Parser', () => { + beforeEach(() => { + this.registery = new VariableRegistery() + this.bodyParser = peg.generate(grammar, { context: { Snippet, Variable, registery: this.registery } }) + }) + it('breaks a snippet body into lines, with each line containing tab stops at the appropriate position', () => { + const snippet = this.bodyParser.parse(`\ the quick brown $1fox \${2:jumped \${3:over} }the \${4:lazy} dog\ -` - ); +`) - expect(bodyTree).toEqual([ - "the quick brown ", - {index: 1, content: []}, - "fox ", - { - index: 2, - content: [ - "jumped ", - {index: 3, content: ["over"]}, - "\n" - ], - }, - "the ", - {index: 4, content: ["lazy"]}, - " dog" - ]); - }); + expect(snippet).toEqual(new Snippet({ + registery: this.registery, + range: [0, 61], + value: [ + 'the quick brown ', + this.registery.add(new Variable({ identifier: '1', range: [16, 18], value: [] })), + 'fox ', + new Variable({ + identifier: '2', + range: [22, 44], + value: [ + 'jumped ', + this.registery.add(new Variable({ identifier: '3', range: [33, 42], value: ['over'] })), + '\n' + ] + }), + 'the ', + this.registery.add(new Variable({ identifier: '4', range: [48, 57], value: ['lazy'] })), + ' dog' + ] + })) + }) it("removes interpolated variables in placeholder text (we don't currently support it)", () => { - const bodyTree = BodyParser.parse("module ${1:ActiveRecord::${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/(?2::\\u$1)/g}}"); + const bodyTree = this.bodyParser.parse('module ${1:ActiveRecord::${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/(?2::\\u$1)/g}}') expect(bodyTree).toEqual([ - "module ", + 'module ', { - "index": 1, - "content": ["ActiveRecord::", ""] + index: 1, + content: ['ActiveRecord::', ''] } - ]); - }); + ]) + }) - it("skips escaped tabstops", () => { - const bodyTree = BodyParser.parse("snippet $1 escaped \\$2 \\\\$3"); + it('skips escaped tabstops', () => { + const bodyTree = this.bodyParser.parse('snippet $1 escaped \\$2 \\\\$3') expect(bodyTree).toEqual([ - "snippet ", + 'snippet ', { index: 1, content: [] }, - " escaped $2 \\", + ' escaped $2 \\', { index: 3, content: [] } - ]); - }); + ]) + }) - it("includes escaped right-braces", () => { - const bodyTree = BodyParser.parse("snippet ${1:{\\}}"); + it('includes escaped right-braces', () => { + const bodyTree = this.bodyParser.parse('snippet ${1:{\\}}') expect(bodyTree).toEqual([ - "snippet ", + 'snippet ', { index: 1, - content: ["{}"] + content: ['{}'] } - ]); - }); + ]) + }) - it("parses a snippet with transformations", () => { - const bodyTree = BodyParser.parse("<${1:p}>$0"); + it('parses a snippet with transformations', () => { + const bodyTree = this.bodyParser.parse('<${1:p}>$0') expect(bodyTree).toEqual([ '<', - {index: 1, content: ['p']}, + { index: 1, content: ['p'] }, '>', - {index: 0, content: []}, + { index: 0, content: [] }, '' - ]); - }); + ]) + }) - it("parses a snippet with multiple tab stops with transformations", () => { - const bodyTree = BodyParser.parse("${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2"); + it('parses a snippet with multiple tab stops with transformations', () => { + const bodyTree = this.bodyParser.parse('${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2') expect(bodyTree).toEqual([ - {index: 1, content: ['placeholder']}, + { index: 1, content: ['placeholder'] }, ' ', { index: 1, @@ -88,15 +105,15 @@ the quick brown $1fox \${2:jumped \${3:over} substitution: { find: /(.)/g, replace: [ - {escape: 'u'}, - {backreference: 1} + { escape: 'u' }, + { backreference: 1 } ] } }, ' ', - {index: 1, content: []}, + { index: 1, content: [] }, ' ', - {index: 2, content: ['ANOTHER']}, + { index: 2, content: ['ANOTHER'] }, ' ', { index: 2, @@ -104,21 +121,20 @@ the quick brown $1fox \${2:jumped \${3:over} substitution: { find: /^(.*)$/g, replace: [ - {escape: 'L'}, - {backreference: 1} + { escape: 'L' }, + { backreference: 1 } ] } }, ' ', - {index: 2, content: []}, - ]); - }); + { index: 2, content: [] } + ]) + }) - - it("parses a snippet with transformations and mirrors", () => { - const bodyTree = BodyParser.parse("${1:placeholder}\n${1/(.)/\\u$1/}\n$1"); + it('parses a snippet with transformations and mirrors', () => { + const bodyTree = this.bodyParser.parse('${1:placeholder}\n${1/(.)/\\u$1/}\n$1') expect(bodyTree).toEqual([ - {index: 1, content: ['placeholder']}, + { index: 1, content: ['placeholder'] }, '\n', { index: 1, @@ -126,23 +142,23 @@ the quick brown $1fox \${2:jumped \${3:over} substitution: { find: /(.)/g, replace: [ - {escape: 'u'}, - {backreference: 1} + { escape: 'u' }, + { backreference: 1 } ] } }, '\n', - {index: 1, content: []} - ]); - }); + { index: 1, content: [] } + ]) + }) - it("parses a snippet with a format string and case-control flags", () => { - const bodyTree = BodyParser.parse("<${1:p}>$0"); + it('parses a snippet with a format string and case-control flags', () => { + const bodyTree = this.bodyParser.parse('<${1:p}>$0') expect(bodyTree).toEqual([ '<', - {index: 1, content: ['p']}, + { index: 1, content: ['p'] }, '>', - {index: 0, content: []}, + { index: 0, content: [] }, '' - ]); - }); + ]) + }) - it("parses a snippet with an escaped forward slash in a transform", () => { + it('parses a snippet with an escaped forward slash in a transform', () => { // Annoyingly, a forward slash needs to be double-backslashed just like the // other escapes. - const bodyTree = BodyParser.parse("<${1:p}>$0"); + const bodyTree = this.bodyParser.parse('<${1:p}>$0') expect(bodyTree).toEqual([ '<', - {index: 1, content: ['p']}, + { index: 1, content: ['p'] }, '>', - {index: 0, content: []}, + { index: 0, content: [] }, '' - ]); - }); + ]) + }) it("parses a snippet with a placeholder that mirrors another tab stop's content", () => { - const bodyTree = BodyParser.parse("$4console.${3:log}('${2:$1}', $1);$0"); + const bodyTree = this.bodyParser.parse("$4console.${3:log}('${2:$1}', $1);$0") expect(bodyTree).toEqual([ - {index: 4, content: []}, + { index: 4, content: [] }, 'console.', - {index: 3, content: ['log']}, + { index: 3, content: ['log'] }, '(\'', { - index: 2, content: [ - {index: 1, content: []} + index: 2, + content: [ + { index: 1, content: [] } ] }, '\', ', - {index: 1, content: []}, + { index: 1, content: [] }, ');', - {index: 0, content: []} - ]); - }); + { index: 0, content: [] } + ]) + }) - it("parses a snippet with a placeholder that mixes text and tab stop references", () => { - const bodyTree = BodyParser.parse("$4console.${3:log}('${2:uh $1}', $1);$0"); + it('parses a snippet with a placeholder that mixes text and tab stop references', () => { + const bodyTree = this.bodyParser.parse("$4console.${3:log}('${2:uh $1}', $1);$0") expect(bodyTree).toEqual([ - {index: 4, content: []}, + { index: 4, content: [] }, 'console.', - {index: 3, content: ['log']}, + { index: 3, content: ['log'] }, '(\'', { - index: 2, content: [ + index: 2, + content: [ 'uh ', - {index: 1, content: []} + { index: 1, content: [] } ] }, '\', ', - {index: 1, content: []}, + { index: 1, content: [] }, ');', - {index: 0, content: []} - ]); - }); -}); + { index: 0, content: [] } + ]) + }) +}) diff --git a/spec/insertion-spec.js b/spec/insertion-spec.js index 83fac925..e2a9d4a4 100644 --- a/spec/insertion-spec.js +++ b/spec/insertion-spec.js @@ -5,30 +5,30 @@ const range = new Range(0, 0) describe('Insertion', () => { it('returns what it was given when it has no substitution', () => { - let insertion = new Insertion({ + const insertion = new Insertion({ range, substitution: undefined }) - let transformed = insertion.transform('foo!') + const transformed = insertion.transform('foo!') expect(transformed).toEqual('foo!') }) it('transforms what it was given when it has a regex transformation', () => { - let insertion = new Insertion({ + const insertion = new Insertion({ range, substitution: { find: /foo/g, replace: ['bar'] } }) - let transformed = insertion.transform('foo!') + const transformed = insertion.transform('foo!') expect(transformed).toEqual('bar!') }) it('transforms the case of the next character when encountering a \\u or \\l flag', () => { - let uInsertion = new Insertion({ + const uInsertion = new Insertion({ range, substitution: { find: /(.)(.)(.*)/g, @@ -45,7 +45,7 @@ describe('Insertion', () => { expect(uInsertion.transform('fOo!')).toEqual('fOo!') expect(uInsertion.transform('FOO!')).toEqual('FOO!') - let lInsertion = new Insertion({ + const lInsertion = new Insertion({ range, substitution: { find: /(.{2})(.)(.*)/g, @@ -65,7 +65,7 @@ describe('Insertion', () => { }) it('transforms the case of all remaining characters when encountering a \\U or \\L flag, up until it sees a \\E flag', () => { - let uInsertion = new Insertion({ + const uInsertion = new Insertion({ range, substitution: { find: /(.)(.*)/, @@ -81,7 +81,7 @@ describe('Insertion', () => { expect(uInsertion.transform('lOREM IPSUM!')).toEqual('lOREM IPSUM!') expect(uInsertion.transform('LOREM IPSUM!')).toEqual('LOREM IPSUM!') - let ueInsertion = new Insertion({ + const ueInsertion = new Insertion({ range, substitution: { find: /(.)(.{3})(.*)/, @@ -99,7 +99,7 @@ describe('Insertion', () => { expect(ueInsertion.transform('lOREm ipsum!')).toEqual('lOREm ipsum!') expect(ueInsertion.transform('LOREM IPSUM!')).toEqual('LOREM IPSUM!') - let lInsertion = new Insertion({ + const lInsertion = new Insertion({ range, substitution: { find: /(.{4})(.)(.*)/, @@ -114,7 +114,7 @@ describe('Insertion', () => { expect(lInsertion.transform('LOREM IPSUM!')).toEqual('LOREmwhat') - let leInsertion = new Insertion({ + const leInsertion = new Insertion({ range, substitution: { find: /^([A-Fa-f])(.*)(.)$/, diff --git a/spec/snippet-loading-spec.js b/spec/snippet-loading-spec.js index d6c5dd18..a544d73c 100644 --- a/spec/snippet-loading-spec.js +++ b/spec/snippet-loading-spec.js @@ -1,130 +1,130 @@ -const path = require('path'); -const fs = require('fs-plus'); -const temp = require('temp').track(); +const path = require('path') +const fs = require('fs-plus') +const temp = require('temp').track() -describe("Snippet Loading", () => { - let configDirPath, snippetsService; +describe('Snippet Loading', () => { + let configDirPath, snippetsService beforeEach(() => { - configDirPath = temp.mkdirSync('atom-config-dir-'); - spyOn(atom, 'getConfigDirPath').andReturn(configDirPath); + configDirPath = temp.mkdirSync('atom-config-dir-') + spyOn(atom, 'getConfigDirPath').andReturn(configDirPath) - spyOn(console, 'warn'); - if (atom.notifications != null) { spyOn(atom.notifications, 'addError'); } + spyOn(console, 'warn') + if (atom.notifications != null) { spyOn(atom.notifications, 'addError') } spyOn(atom.packages, 'getLoadedPackages').andReturn([ atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets')), - atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-broken-snippets')), - ]); - }); + atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-broken-snippets')) + ]) + }) afterEach(() => { - waitsForPromise(() => Promise.resolve(atom.packages.deactivatePackages('snippets'))); + waitsForPromise(() => Promise.resolve(atom.packages.deactivatePackages('snippets'))) runs(() => { - jasmine.unspy(atom.packages, 'getLoadedPackages'); - }); - }); + jasmine.unspy(atom.packages, 'getLoadedPackages') + }) + }) const activateSnippetsPackage = () => { - waitsForPromise(() => atom.packages.activatePackage("snippets").then(({mainModule}) => { - snippetsService = mainModule.provideSnippets(); - mainModule.loaded = false; - })); + waitsForPromise(() => atom.packages.activatePackage('snippets').then(({ mainModule }) => { + snippetsService = mainModule.provideSnippets() + mainModule.loaded = false + })) - waitsFor("all snippets to load", 3000, () => snippetsService.bundledSnippetsLoaded()); - }; + waitsFor('all snippets to load', 3000, () => snippetsService.bundledSnippetsLoaded()) + } - it("loads the bundled snippet template snippets", () => { - activateSnippetsPackage(); + it('loads the bundled snippet template snippets', () => { + activateSnippetsPackage() runs(() => { - const jsonSnippet = snippetsService.snippetsForScopes(['.source.json'])['snip']; - expect(jsonSnippet.name).toBe('Atom Snippet'); - expect(jsonSnippet.prefix).toBe('snip'); - expect(jsonSnippet.body).toContain('"prefix":'); - expect(jsonSnippet.body).toContain('"body":'); - expect(jsonSnippet.tabStopList.length).toBeGreaterThan(0); - - const csonSnippet = snippetsService.snippetsForScopes(['.source.coffee'])['snip']; - expect(csonSnippet.name).toBe('Atom Snippet'); - expect(csonSnippet.prefix).toBe('snip'); - expect(csonSnippet.body).toContain("'prefix':"); - expect(csonSnippet.body).toContain("'body':"); - expect(csonSnippet.tabStopList.length).toBeGreaterThan(0); - }); - }); - - it("loads non-hidden snippet files from atom packages with snippets directories", () => { - activateSnippetsPackage(); + const jsonSnippet = snippetsService.snippetsForScopes(['.source.json']).snip + expect(jsonSnippet.name).toBe('Atom Snippet') + expect(jsonSnippet.prefix).toBe('snip') + expect(jsonSnippet.body).toContain('"prefix":') + expect(jsonSnippet.body).toContain('"body":') + expect(jsonSnippet.tabStopList.length).toBeGreaterThan(0) + + const csonSnippet = snippetsService.snippetsForScopes(['.source.coffee']).snip + expect(csonSnippet.name).toBe('Atom Snippet') + expect(csonSnippet.prefix).toBe('snip') + expect(csonSnippet.body).toContain("'prefix':") + expect(csonSnippet.body).toContain("'body':") + expect(csonSnippet.tabStopList.length).toBeGreaterThan(0) + }) + }) + + it('loads non-hidden snippet files from atom packages with snippets directories', () => { + activateSnippetsPackage() runs(() => { - let snippet = snippetsService.snippetsForScopes(['.test'])['test']; - expect(snippet.prefix).toBe('test'); - expect(snippet.body).toBe('testing 123'); - - snippet = snippetsService.snippetsForScopes(['.test'])['testd']; - expect(snippet.prefix).toBe('testd'); - expect(snippet.body).toBe('testing 456'); - expect(snippet.description).toBe('a description'); - expect(snippet.descriptionMoreURL).toBe('http://google.com'); - - snippet = snippetsService.snippetsForScopes(['.test'])['testlabelleft']; - expect(snippet.prefix).toBe('testlabelleft'); - expect(snippet.body).toBe('testing 456'); - expect(snippet.leftLabel).toBe('a label'); - - snippet = snippetsService.snippetsForScopes(['.test'])['testhtmllabels']; - expect(snippet.prefix).toBe('testhtmllabels'); - expect(snippet.body).toBe('testing 456'); - expect(snippet.leftLabelHTML).toBe('Label'); - expect(snippet.rightLabelHTML).toBe('Label'); - }); - }); - - it("logs a warning if package snippets files cannot be parsed", () => { - activateSnippetsPackage(); + let snippet = snippetsService.snippetsForScopes(['.test']).test + expect(snippet.prefix).toBe('test') + expect(snippet.body).toBe('testing 123') + + snippet = snippetsService.snippetsForScopes(['.test']).testd + expect(snippet.prefix).toBe('testd') + expect(snippet.body).toBe('testing 456') + expect(snippet.description).toBe('a description') + expect(snippet.descriptionMoreURL).toBe('http://google.com') + + snippet = snippetsService.snippetsForScopes(['.test']).testlabelleft + expect(snippet.prefix).toBe('testlabelleft') + expect(snippet.body).toBe('testing 456') + expect(snippet.leftLabel).toBe('a label') + + snippet = snippetsService.snippetsForScopes(['.test']).testhtmllabels + expect(snippet.prefix).toBe('testhtmllabels') + expect(snippet.body).toBe('testing 456') + expect(snippet.leftLabelHTML).toBe('Label') + expect(snippet.rightLabelHTML).toBe('Label') + }) + }) + + it('logs a warning if package snippets files cannot be parsed', () => { + activateSnippetsPackage() runs(() => { // Warn about invalid-file, but don't even try to parse a hidden file - expect(console.warn.calls.length).toBeGreaterThan(0); - expect(console.warn.mostRecentCall.args[0]).toMatch(/Error reading.*package-with-broken-snippets/); - }); - }); + expect(console.warn.calls.length).toBeGreaterThan(0) + expect(console.warn.mostRecentCall.args[0]).toMatch(/Error reading.*package-with-broken-snippets/) + }) + }) - describe("::loadPackageSnippets(callback)", () => { + describe('::loadPackageSnippets(callback)', () => { beforeEach(() => { // simulate a list of packages where the javascript core package is returned at the end - atom.packages.getLoadedPackages.andReturn([ + atom.packages.getLoadedPackages.andReturn([ atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets')), atom.packages.loadPackage('language-javascript') ]) - }); + }) it("allows other packages to override core packages' snippets", () => { - waitsForPromise(() => atom.packages.activatePackage("language-javascript")); + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - const snippet = snippetsService.snippetsForScopes(['.source.js'])['log']; - expect(snippet.body).toBe("from-a-community-package"); - }); - }); - }); - - describe("::onDidLoadSnippets(callback)", () => { - it("invokes listeners when all snippets are loaded", () => { - let loadedCallback = null; - - waitsFor("package to activate", done => atom.packages.activatePackage("snippets").then(({mainModule}) => { - mainModule.onDidLoadSnippets(loadedCallback = jasmine.createSpy('onDidLoadSnippets callback')); - done(); - })); - - waitsFor("onDidLoad callback to be called", () => loadedCallback.callCount > 0); - }); - }); - - describe("when ~/.atom/snippets.json exists", () => { + const snippet = snippetsService.snippetsForScopes(['.source.js']).log + expect(snippet.body).toBe('from-a-community-package') + }) + }) + }) + + describe('::onDidLoadSnippets(callback)', () => { + it('invokes listeners when all snippets are loaded', () => { + let loadedCallback = null + + waitsFor('package to activate', done => atom.packages.activatePackage('snippets').then(({ mainModule }) => { + mainModule.onDidLoadSnippets(loadedCallback = jasmine.createSpy('onDidLoadSnippets callback')) + done() + })) + + waitsFor('onDidLoad callback to be called', () => loadedCallback.callCount > 0) + }) + }) + + describe('when ~/.atom/snippets.json exists', () => { beforeEach(() => { fs.writeFileSync(path.join(configDirPath, 'snippets.json'), `\ { @@ -136,24 +136,24 @@ describe("Snippet Loading", () => { } }\ ` - ); - activateSnippetsPackage(); - }); + ) + activateSnippetsPackage() + }) - it("loads the snippets from that file", () => { - let snippet = null; + it('loads the snippets from that file', () => { + let snippet = null - waitsFor(() => snippet = snippetsService.snippetsForScopes(['.foo'])['foo']); + waitsFor(() => (snippet = snippetsService.snippetsForScopes(['.foo']).foo)) runs(() => { - expect(snippet.name).toBe('foo snippet'); - expect(snippet.prefix).toBe("foo"); - expect(snippet.body).toBe("bar1"); - }); - }); - - describe("when that file changes", () => { - it("reloads the snippets", () => { + expect(snippet.name).toBe('foo snippet') + expect(snippet.prefix).toBe('foo') + expect(snippet.body).toBe('bar1') + }) + }) + + describe('when that file changes', () => { + it('reloads the snippets', () => { fs.writeFileSync(path.join(configDirPath, 'snippets.json'), `\ { ".foo": { @@ -164,23 +164,23 @@ describe("Snippet Loading", () => { } }\ ` - ); + ) - waitsFor("snippets to be changed", () => { - const snippet = snippetsService.snippetsForScopes(['.foo'])['foo']; - return snippet && snippet.body === 'bar2'; - }); + waitsFor('snippets to be changed', () => { + const snippet = snippetsService.snippetsForScopes(['.foo']).foo + return snippet && snippet.body === 'bar2' + }) runs(() => { - fs.writeFileSync(path.join(configDirPath, 'snippets.json'), ""); - }); + fs.writeFileSync(path.join(configDirPath, 'snippets.json'), '') + }) - waitsFor("snippets to be removed", () => !snippetsService.snippetsForScopes(['.foo'])['foo']); - }); - }); - }); + waitsFor('snippets to be removed', () => !snippetsService.snippetsForScopes(['.foo']).foo) + }) + }) + }) - describe("when ~/.atom/snippets.cson exists", () => { + describe('when ~/.atom/snippets.cson exists', () => { beforeEach(() => { fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), `\ ".foo": @@ -188,122 +188,122 @@ describe("Snippet Loading", () => { "prefix": "foo" "body": "bar1"\ ` - ); - activateSnippetsPackage(); - }); + ) + activateSnippetsPackage() + }) - it("loads the snippets from that file", () => { - let snippet = null; + it('loads the snippets from that file', () => { + let snippet = null - waitsFor(() => snippet = snippetsService.snippetsForScopes(['.foo'])['foo']); + waitsFor(() => (snippet = snippetsService.snippetsForScopes(['.foo']).foo)) runs(() => { - expect(snippet.name).toBe('foo snippet'); - expect(snippet.prefix).toBe("foo"); - expect(snippet.body).toBe("bar1"); - }); - }); - - describe("when that file changes", () => { - it("reloads the snippets", () => { + expect(snippet.name).toBe('foo snippet') + expect(snippet.prefix).toBe('foo') + expect(snippet.body).toBe('bar1') + }) + }) + + describe('when that file changes', () => { + it('reloads the snippets', () => { fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), `\ ".foo": "foo snippet": "prefix": "foo" "body": "bar2"\ ` - ); + ) - waitsFor("snippets to be changed", () => { - const snippet = snippetsService.snippetsForScopes(['.foo'])['foo']; - return snippet && snippet.body === 'bar2'; - }); + waitsFor('snippets to be changed', () => { + const snippet = snippetsService.snippetsForScopes(['.foo']).foo + return snippet && snippet.body === 'bar2' + }) runs(() => { - fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), ""); - }); + fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), '') + }) - waitsFor("snippets to be removed", () => { - const snippet = snippetsService.snippetsForScopes(['.foo'])['foo']; - return snippet == null; - }); - }); - }); - }); + waitsFor('snippets to be removed', () => { + const snippet = snippetsService.snippetsForScopes(['.foo']).foo + return snippet == null + }) + }) + }) + }) - it("notifies the user when the user snippets file cannot be loaded", () => { - fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), '".junk":::'); + it('notifies the user when the user snippets file cannot be loaded', () => { + fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), '".junk":::') - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - expect(console.warn).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalled() if (atom.notifications != null) { - expect(atom.notifications.addError).toHaveBeenCalled(); + expect(atom.notifications.addError).toHaveBeenCalled() } - }); - }); + }) + }) - describe("packages-with-snippets-disabled feature", () => { - it("disables no snippets if the config option is empty", () => { - const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled'); - atom.config.set('core.packagesWithSnippetsDisabled', []); + describe('packages-with-snippets-disabled feature', () => { + it('disables no snippets if the config option is empty', () => { + const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled') + atom.config.set('core.packagesWithSnippetsDisabled', []) - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']); - expect(Object.keys(snippets).length).toBe(1); - atom.config.set('core.packagesWithSnippetsDisabled', originalConfig); - }); - }); + const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']) + expect(Object.keys(snippets).length).toBe(1) + atom.config.set('core.packagesWithSnippetsDisabled', originalConfig) + }) + }) it("still includes a disabled package's snippets in the list of unparsed snippets", () => { - let originalConfig = atom.config.get('core.packagesWithSnippetsDisabled'); - atom.config.set('core.packagesWithSnippetsDisabled', []); + const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled') + atom.config.set('core.packagesWithSnippetsDisabled', []) - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']); - const allSnippets = snippetsService.getUnparsedSnippets(); - const scopedSnippet = allSnippets.find(s => s.selectorString === '.package-with-snippets-unique-scope'); - expect(scopedSnippet).not.toBe(undefined); - atom.config.set('core.packagesWithSnippetsDisabled', originalConfig); - }); - }); + atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']) + const allSnippets = snippetsService.getUnparsedSnippets() + const scopedSnippet = allSnippets.find(s => s.selectorString === '.package-with-snippets-unique-scope') + expect(scopedSnippet).not.toBe(undefined) + atom.config.set('core.packagesWithSnippetsDisabled', originalConfig) + }) + }) it("never loads a package's snippets when that package is disabled in config", () => { - const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled'); - atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']); + const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled') + atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']) - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']); - expect(Object.keys(snippets).length).toBe(0); - atom.config.set('core.packagesWithSnippetsDisabled', originalConfig); - }); - }); + const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']) + expect(Object.keys(snippets).length).toBe(0) + atom.config.set('core.packagesWithSnippetsDisabled', originalConfig) + }) + }) - it("unloads and/or reloads snippets from a package if the config option is changed after activation", () => { - const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled'); - atom.config.set('core.packagesWithSnippetsDisabled', []); + it('unloads and/or reloads snippets from a package if the config option is changed after activation', () => { + const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled') + atom.config.set('core.packagesWithSnippetsDisabled', []) - activateSnippetsPackage(); + activateSnippetsPackage() runs(() => { - let snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']); - expect(Object.keys(snippets).length).toBe(1); + let snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']) + expect(Object.keys(snippets).length).toBe(1) // Disable it. - atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']); - snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']); - expect(Object.keys(snippets).length).toBe(0); + atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']) + snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']) + expect(Object.keys(snippets).length).toBe(0) // Re-enable it. - atom.config.set('core.packagesWithSnippetsDisabled', []); - snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']); - expect(Object.keys(snippets).length).toBe(1); - - atom.config.set('core.packagesWithSnippetsDisabled', originalConfig); - }); - }); - }); -}); + atom.config.set('core.packagesWithSnippetsDisabled', []) + snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']) + expect(Object.keys(snippets).length).toBe(1) + + atom.config.set('core.packagesWithSnippetsDisabled', originalConfig) + }) + }) + }) +}) diff --git a/spec/snippets-spec.js b/spec/snippets-spec.js index 68994478..9c1fae84 100644 --- a/spec/snippets-spec.js +++ b/spec/snippets-spec.js @@ -1,152 +1,154 @@ -const path = require('path'); -const temp = require('temp').track(); -const Snippets = require('../lib/snippets'); -const {TextEditor} = require('atom'); +/* eslint no-template-curly-in-string: 0 */ -describe("Snippets extension", () => { - let editorElement, editor; +const path = require('path') +const temp = require('temp').track() +const Snippets = require('../lib/snippets') +const { TextEditor } = require('atom') + +describe('Snippets extension', () => { + let editorElement, editor const simulateTabKeyEvent = (param) => { if (param == null) { - param = {}; + param = {} } - const {shift} = param; - const event = atom.keymaps.constructor.buildKeydownEvent('tab', {shift, target: editorElement}); - atom.keymaps.handleKeyboardEvent(event); - }; + const { shift } = param + const event = atom.keymaps.constructor.buildKeydownEvent('tab', { shift, target: editorElement }) + atom.keymaps.handleKeyboardEvent(event) + } beforeEach(() => { - spyOn(Snippets, 'loadAll'); - spyOn(Snippets, 'getUserSnippetsPath').andReturn(''); + spyOn(Snippets, 'loadAll') + spyOn(Snippets, 'getUserSnippetsPath').andReturn('') - waitsForPromise(() => atom.workspace.open('sample.js')); - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); - waitsForPromise(() => atom.packages.activatePackage('snippets')); + waitsForPromise(() => atom.workspace.open('sample.js')) + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) + waitsForPromise(() => atom.packages.activatePackage('snippets')) runs(() => { - editor = atom.workspace.getActiveTextEditor(); - editorElement = atom.views.getView(editor); - }); - }); + editor = atom.workspace.getActiveTextEditor() + editorElement = atom.views.getView(editor) + }) + }) afterEach(() => { - waitsForPromise(() => atom.packages.deactivatePackage('snippets')); - }); + waitsForPromise(() => atom.packages.deactivatePackage('snippets')) + }) - describe("provideSnippets interface", () => { - let snippetsInterface = null; + describe('provideSnippets interface', () => { + let snippetsInterface = null beforeEach(() => { - snippetsInterface = Snippets.provideSnippets(); - }); + snippetsInterface = Snippets.provideSnippets() + }) - describe("bundledSnippetsLoaded", () => { - it("indicates the loaded state of the bundled snippets", () => { - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false); - Snippets.doneLoading(); - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true); - }); + describe('bundledSnippetsLoaded', () => { + it('indicates the loaded state of the bundled snippets', () => { + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false) + Snippets.doneLoading() + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true) + }) - it("resets the loaded state after snippets is deactivated", () => { - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false); - Snippets.doneLoading(); - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true); + it('resets the loaded state after snippets is deactivated', () => { + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false) + Snippets.doneLoading() + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true) - waitsForPromise(() => atom.packages.deactivatePackage('snippets')); - waitsForPromise(() => atom.packages.activatePackage('snippets')); + waitsForPromise(() => atom.packages.deactivatePackage('snippets')) + waitsForPromise(() => atom.packages.activatePackage('snippets')) runs(() => { - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false); - Snippets.doneLoading(); - expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true); - }); - }); - }); - - describe("insertSnippet", () => { - it("can insert a snippet", () => { - editor.setSelectedBufferRange([[0, 4], [0, 13]]); - snippetsInterface.insertSnippet("hello ${1:world}", editor); - expect(editor.lineTextForBufferRow(0)).toBe("var hello world = function () {"); - }); - }); - }); - - it("returns false for snippetToExpandUnderCursor if getSnippets returns {}", () => { - const snippets = atom.packages.getActivePackage('snippets').mainModule; - expect(snippets.snippetToExpandUnderCursor(editor)).toEqual(false); - }); - - it("ignores invalid snippets in the config", () => { - const snippets = atom.packages.getActivePackage('snippets').mainModule; - - let invalidSnippets = null; - spyOn(snippets.scopedPropertyStore, 'getPropertyValue').andCallFake(() => invalidSnippets); - expect(snippets.getSnippets(editor)).toEqual({}); - - invalidSnippets = 'test'; - expect(snippets.getSnippets(editor)).toEqual({}); - - invalidSnippets = []; - expect(snippets.getSnippets(editor)).toEqual({}); - - invalidSnippets = 3; - expect(snippets.getSnippets(editor)).toEqual({}); - - invalidSnippets = {a: null}; - expect(snippets.getSnippets(editor)).toEqual({}); - }); - - describe("when null snippets are present", () => { + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false) + Snippets.doneLoading() + expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true) + }) + }) + }) + + describe('insertSnippet', () => { + it('can insert a snippet', () => { + editor.setSelectedBufferRange([[0, 4], [0, 13]]) + snippetsInterface.insertSnippet('hello ${1:world}', editor) + expect(editor.lineTextForBufferRow(0)).toBe('var hello world = function () {') + }) + }) + }) + + it('returns false for snippetToExpandUnderCursor if getSnippets returns {}', () => { + const snippets = atom.packages.getActivePackage('snippets').mainModule + expect(snippets.snippetToExpandUnderCursor(editor)).toEqual(false) + }) + + it('ignores invalid snippets in the config', () => { + const snippets = atom.packages.getActivePackage('snippets').mainModule + + let invalidSnippets = null + spyOn(snippets.scopedPropertyStore, 'getPropertyValue').andCallFake(() => invalidSnippets) + expect(snippets.getSnippets(editor)).toEqual({}) + + invalidSnippets = 'test' + expect(snippets.getSnippets(editor)).toEqual({}) + + invalidSnippets = [] + expect(snippets.getSnippets(editor)).toEqual({}) + + invalidSnippets = 3 + expect(snippets.getSnippets(editor)).toEqual({}) + + invalidSnippets = { a: null } + expect(snippets.getSnippets(editor)).toEqual({}) + }) + + describe('when null snippets are present', () => { beforeEach(() => Snippets.add(__filename, { - ".source.js": { - "some snippet": { - prefix: "t1", - body: "this is a test" + '.source.js': { + 'some snippet': { + prefix: 't1', + body: 'this is a test' } }, - ".source.js .nope": { - "some snippet": { - prefix: "t1", + '.source.js .nope': { + 'some snippet': { + prefix: 't1', body: null } } - })); + })) - it("overrides the less-specific defined snippet", () => { - const snippets = Snippets.provideSnippets(); - expect(snippets.snippetsForScopes(['.source.js'])['t1']).toBeTruthy(); - expect(snippets.snippetsForScopes(['.source.js .nope.not-today'])['t1']).toBeFalsy(); - }); - }); + it('overrides the less-specific defined snippet', () => { + const snippets = Snippets.provideSnippets() + expect(snippets.snippetsForScopes(['.source.js']).t1).toBeTruthy() + expect(snippets.snippetsForScopes(['.source.js .nope.not-today']).t1).toBeFalsy() + }) + }) describe("when 'tab' is triggered on the editor", () => { beforeEach(() => { Snippets.add(__filename, { - ".source.js": { - "without tab stops": { - prefix: "t1", - body: "this is a test" + '.source.js': { + 'without tab stops': { + prefix: 't1', + body: 'this is a test' }, - "with only an end tab stop": { - prefix: "t1a", - body: "something $0 strange" + 'with only an end tab stop': { + prefix: 't1a', + body: 'something $0 strange' }, - "overlapping prefix": { - prefix: "tt1", - body: "this is another test" + 'overlapping prefix': { + prefix: 'tt1', + body: 'this is another test' }, - "special chars": { - prefix: "@unique", - body: "@unique see" + 'special chars': { + prefix: '@unique', + body: '@unique see' }, - "tab stops": { - prefix: "t2", + 'tab stops': { + prefix: 't2', body: `\ go here next:($2) and finally go here:($0) go here first:($1) @@ -154,8 +156,8 @@ go here first:($1) ` }, - "indented second line": { - prefix: "t3", + 'indented second line': { + prefix: 't3', body: `\ line 1 \tline 2$1 @@ -163,16 +165,16 @@ $2\ ` }, - "multiline with indented placeholder tabstop": { - prefix: "t4", + 'multiline with indented placeholder tabstop': { + prefix: 't4', body: `\ line \${1:1} \${2:body...}\ ` }, - "multiline starting with tabstop": { - prefix: "t4b", + 'multiline starting with tabstop': { + prefix: 't4b', body: `\ $1 = line 1 { line 2 @@ -180,31 +182,31 @@ $1 = line 1 { ` }, - "nested tab stops": { - prefix: "t5", + 'nested tab stops': { + prefix: 't5', body: '${1:"${2:key}"}: ${3:value}' }, - "caused problems with undo": { - prefix: "t6", + 'caused problems with undo': { + prefix: 't6', body: `\ first line$1 \${2:placeholder ending second line}\ ` }, - "tab stops at beginning and then end of snippet": { - prefix: "t6b", - body: "$1expanded$0" + 'tab stops at beginning and then end of snippet': { + prefix: 't6b', + body: '$1expanded$0' }, - "tab stops at end and then beginning of snippet": { - prefix: "t6c", - body: "$0expanded$1" + 'tab stops at end and then beginning of snippet': { + prefix: 't6c', + body: '$0expanded$1' }, - "contains empty lines": { - prefix: "t7", + 'contains empty lines': { + prefix: 't7', body: `\ first line $1 @@ -212,24 +214,24 @@ first line $1 fourth line after blanks $2\ ` }, - "with/without placeholder": { - prefix: "t8", + 'with/without placeholder': { + prefix: 't8', body: `\ with placeholder \${1:test} without placeholder \${2}\ ` }, - "multi-caret": { - prefix: "t9", + 'multi-caret': { + prefix: 't9', body: `\ with placeholder \${1:test} without placeholder $1\ ` }, - "multi-caret-multi-tabstop": { - prefix: "t9b", + 'multi-caret-multi-tabstop': { + prefix: 't9b', body: `\ with placeholder \${1:test} without placeholder $1 @@ -238,974 +240,966 @@ third tabstop $3\ ` }, - "large indices": { - prefix: "t10", - body: "hello${10} ${11:large} indices${1}" + 'large indices': { + prefix: 't10', + body: 'hello${10} ${11:large} indices${1}' }, - "no body": { - prefix: "bad1" + 'no body': { + prefix: 'bad1' }, - "number body": { - prefix: "bad2", + 'number body': { + prefix: 'bad2', body: 100 }, - "many tabstops": { - prefix: "t11", - body: "$0one${1} ${2:two} three${3}" + 'many tabstops': { + prefix: 't11', + body: '$0one${1} ${2:two} three${3}' }, - "simple transform": { - prefix: "t12", - body: "[${1:b}][/${1/[ ]+.*$//}]" + 'simple transform': { + prefix: 't12', + body: '[${1:b}][/${1/[ ]+.*$//}]' }, - "transform with non-transforming mirrors": { - prefix: "t13", - body: "${1:placeholder}\n${1/(.)/\\u$1/}\n$1" + 'transform with non-transforming mirrors': { + prefix: 't13', + body: '${1:placeholder}\n${1/(.)/\\u$1/}\n$1' }, - "multiple tab stops, some with transforms and some without": { - prefix: "t14", - body: "${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2" + 'multiple tab stops, some with transforms and some without': { + prefix: 't14', + body: '${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2' }, - "has a transformed tab stop without a corresponding ordinary tab stop": { + 'has a transformed tab stop without a corresponding ordinary tab stop': { prefix: 't15', - body: "${1/(.)/\\u$1/} & $2" + body: '${1/(.)/\\u$1/} & $2' }, - "has a transformed tab stop that occurs before the corresponding ordinary tab stop": { + 'has a transformed tab stop that occurs before the corresponding ordinary tab stop': { prefix: 't16', - body: "& ${1/(.)/\\u$1/} & ${1:q}" + body: '& ${1/(.)/\\u$1/} & ${1:q}' }, "has a placeholder that mirrors another tab stop's content": { prefix: 't17', body: "$4console.${3:log}('${2:uh $1}', $1);$0" }, - "has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step": { + 'has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step': { prefix: 't18', body: '// $1\n// ${1/./=/}' } } - }); - }); - - it("parses snippets once, reusing cached ones on subsequent queries", () => { - spyOn(Snippets, "getBodyParser").andCallThrough(); - - editor.insertText("t1"); - simulateTabKeyEvent(); - - expect(Snippets.getBodyParser).toHaveBeenCalled(); - expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 14]); + }) + }) - Snippets.getBodyParser.reset(); + it('parses snippets once, reusing cached ones on subsequent queries', () => { + // TODO: Once Jasmin >= 2.6.1, ensure the property is invoked when appropriate + editor.insertText('t1') + simulateTabKeyEvent() - editor.setText(""); - editor.insertText("t1"); - simulateTabKeyEvent(); + expect(editor.lineTextForBufferRow(0)).toBe('this is a testvar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 14]) - expect(Snippets.getBodyParser).not.toHaveBeenCalled(); - expect(editor.lineTextForBufferRow(0)).toBe("this is a test"); - expect(editor.getCursorScreenPosition()).toEqual([0, 14]); + editor.setText('') + editor.insertText('t1') + simulateTabKeyEvent() - Snippets.getBodyParser.reset(); + expect(editor.lineTextForBufferRow(0)).toBe('this is a test') + expect(editor.getCursorScreenPosition()).toEqual([0, 14]) Snippets.add(__filename, { - ".source.js": { - "invalidate previous snippet": { - prefix: "t1", - body: "new snippet" + '.source.js': { + 'invalidate previous snippet': { + prefix: 't1', + body: 'new snippet' } } - }); - - editor.setText(""); - editor.insertText("t1"); - simulateTabKeyEvent(); - - expect(Snippets.getBodyParser).toHaveBeenCalled(); - expect(editor.lineTextForBufferRow(0)).toBe("new snippet"); - expect(editor.getCursorScreenPosition()).toEqual([0, 11]); - }); - - describe("when the snippet body is invalid or missing", () => { - it("does not register the snippet", () => { - editor.setText(''); - editor.insertText('bad1'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.getText()).toBe('bad1'); - - editor.setText(''); - editor.setText('bad2'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.getText()).toBe('bad2'); - }); - }); - - describe("when the letters preceding the cursor trigger a snippet", () => { - describe("when the snippet contains no tab stops", () => { - it("replaces the prefix with the snippet text and places the cursor at its end", () => { - editor.insertText("t1"); - expect(editor.getCursorScreenPosition()).toEqual([0, 2]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 14]); - }); - - it("inserts a real tab the next time a tab is pressed after the snippet is expanded", () => { - editor.insertText("t1"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("this is a test var quicksort = function () {"); - }); - }); - - describe("when the snippet contains tab stops", () => { + }) + + editor.setText('') + editor.insertText('t1') + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('new snippet') + expect(editor.getCursorScreenPosition()).toEqual([0, 11]) + }) + + describe('when the snippet body is invalid or missing', () => { + it('does not register the snippet', () => { + editor.setText('') + editor.insertText('bad1') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.getText()).toBe('bad1') + + editor.setText('') + editor.setText('bad2') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.getText()).toBe('bad2') + }) + }) + + describe('when the letters preceding the cursor trigger a snippet', () => { + describe('when the snippet contains no tab stops', () => { + it('replaces the prefix with the snippet text and places the cursor at its end', () => { + editor.insertText('t1') + expect(editor.getCursorScreenPosition()).toEqual([0, 2]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('this is a testvar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 14]) + }) + + it('inserts a real tab the next time a tab is pressed after the snippet is expanded', () => { + editor.insertText('t1') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('this is a testvar quicksort = function () {') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('this is a test var quicksort = function () {') + }) + }) + + describe('when the snippet contains tab stops', () => { it("places the cursor at the first tab-stop, and moves the cursor in response to 'next-tab-stop' events", () => { - const markerCountBefore = editor.getMarkerCount(); - editor.setCursorScreenPosition([2, 0]); - editor.insertText('t2'); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(2)).toBe("go here next:() and finally go here:()"); - expect(editor.lineTextForBufferRow(3)).toBe("go here first:()"); - expect(editor.lineTextForBufferRow(4)).toBe(" if (items.length <= 1) return items;"); - expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]); - - simulateTabKeyEvent(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 14]]); - editor.insertText('abc'); - - simulateTabKeyEvent(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 40], [2, 40]]); + const markerCountBefore = editor.getMarkerCount() + editor.setCursorScreenPosition([2, 0]) + editor.insertText('t2') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(2)).toBe('go here next:() and finally go here:()') + expect(editor.lineTextForBufferRow(3)).toBe('go here first:()') + expect(editor.lineTextForBufferRow(4)).toBe(' if (items.length <= 1) return items;') + expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]) + + simulateTabKeyEvent() + expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 14]]) + editor.insertText('abc') + + simulateTabKeyEvent() + expect(editor.getSelectedBufferRange()).toEqual([[2, 40], [2, 40]]) // tab backwards - simulateTabKeyEvent({shift: true}); - expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 17]]); // should highlight text typed at tab stop + simulateTabKeyEvent({ shift: true }) + expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 17]]) // should highlight text typed at tab stop - simulateTabKeyEvent({shift: true}); - expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]); + simulateTabKeyEvent({ shift: true }) + expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]) // shift-tab on first tab-stop does nothing - simulateTabKeyEvent({shift: true}); - expect(editor.getCursorScreenPosition()).toEqual([3, 15]); + simulateTabKeyEvent({ shift: true }) + expect(editor.getCursorScreenPosition()).toEqual([3, 15]) // tab through all tab stops, then tab on last stop to terminate snippet - simulateTabKeyEvent(); - simulateTabKeyEvent(); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(2)).toBe("go here next:(abc) and finally go here:( )"); - expect(editor.getMarkerCount()).toBe(markerCountBefore); - }); - - describe("when tab stops are nested", () => { - it("destroys the inner tab stop if the outer tab stop is modified", () => { - editor.setText(''); - editor.insertText('t5'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.lineTextForBufferRow(0)).toBe('"key": value'); - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 5]]); - editor.insertText("foo"); - simulateTabKeyEvent(); - expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 10]]); - }); - }); - - describe("when the only tab stop is an end stop", () => { - it("terminates the snippet immediately after moving the cursor to the end stop", () => { - editor.setText(''); - editor.insertText('t1a'); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("something strange"); - expect(editor.getCursorBufferPosition()).toEqual([0, 10]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("something strange"); - expect(editor.getCursorBufferPosition()).toEqual([0, 12]); - }); - }); - - describe("when tab stops are separated by blank lines", () => { - it("correctly places the tab stops (regression)", () => { - editor.setText(''); - editor.insertText('t7'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - atom.commands.dispatch(editorElement, 'snippets:next-tab-stop'); - expect(editor.getCursorBufferPosition()).toEqual([3, 25]); - }); - }); - - describe("when the cursor is moved beyond the bounds of the current tab stop", () => { - it("terminates the snippet", () => { - editor.setCursorScreenPosition([2, 0]); - editor.insertText('t2'); - simulateTabKeyEvent(); - - editor.moveUp(); - editor.moveLeft(); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(2)).toBe("go here next:( ) and finally go here:()"); - expect(editor.getCursorBufferPosition()).toEqual([2, 16]); + simulateTabKeyEvent() + simulateTabKeyEvent() + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(2)).toBe('go here next:(abc) and finally go here:( )') + expect(editor.getMarkerCount()).toBe(markerCountBefore) + }) + + describe('when tab stops are nested', () => { + it('destroys the inner tab stop if the outer tab stop is modified', () => { + editor.setText('') + editor.insertText('t5') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.lineTextForBufferRow(0)).toBe('"key": value') + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 5]]) + editor.insertText('foo') + simulateTabKeyEvent() + expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 10]]) + }) + }) + + describe('when the only tab stop is an end stop', () => { + it('terminates the snippet immediately after moving the cursor to the end stop', () => { + editor.setText('') + editor.insertText('t1a') + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('something strange') + expect(editor.getCursorBufferPosition()).toEqual([0, 10]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('something strange') + expect(editor.getCursorBufferPosition()).toEqual([0, 12]) + }) + }) + + describe('when tab stops are separated by blank lines', () => { + it('correctly places the tab stops (regression)', () => { + editor.setText('') + editor.insertText('t7') + atom.commands.dispatch(editorElement, 'snippets:expand') + atom.commands.dispatch(editorElement, 'snippets:next-tab-stop') + expect(editor.getCursorBufferPosition()).toEqual([3, 25]) + }) + }) + + describe('when the cursor is moved beyond the bounds of the current tab stop', () => { + it('terminates the snippet', () => { + editor.setCursorScreenPosition([2, 0]) + editor.insertText('t2') + simulateTabKeyEvent() + + editor.moveUp() + editor.moveLeft() + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(2)).toBe('go here next:( ) and finally go here:()') + expect(editor.getCursorBufferPosition()).toEqual([2, 16]) // test we can terminate with shift-tab - editor.setCursorScreenPosition([4, 0]); - editor.insertText('t2'); - simulateTabKeyEvent(); - simulateTabKeyEvent(); - - editor.moveRight(); - simulateTabKeyEvent({shift: true}); - expect(editor.getCursorBufferPosition()).toEqual([4, 15]); - }); - }); - - describe("when the cursor is moved within the bounds of the current tab stop", () => { - it("should not terminate the snippet", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t8'); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test"); - editor.moveRight(); - editor.moveLeft(); - editor.insertText("foo"); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder tesfoot"); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {"); - editor.insertText("test"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder testvar quicksort = function () {"); - editor.moveLeft(); - editor.insertText("foo"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder tesfootvar quicksort = function () {"); - }); - }); - - describe("when the backspace is press within the bounds of the current tab stop", () => { - it("should not terminate the snippet", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t8'); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test"); - editor.moveRight(); - editor.backspace(); - editor.insertText("foo"); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder tesfoo"); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {"); - editor.insertText("test"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder testvar quicksort = function () {"); - editor.backspace(); - editor.insertText("foo"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder tesfoovar quicksort = function () {"); - }); - }); - }); - - describe("when the snippet contains hard tabs", () => { - describe("when the edit session is in soft-tabs mode", () => { - it("translates hard tabs in the snippet to the appropriate number of spaces", () => { - expect(editor.getSoftTabs()).toBeTruthy(); - editor.insertText("t3"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(1)).toBe(" line 2"); - expect(editor.getCursorBufferPosition()).toEqual([1, 8]); - }); - }); - - describe("when the edit session is in hard-tabs mode", () => { - it("inserts hard tabs in the snippet directly", () => { - editor.setSoftTabs(false); - editor.insertText("t3"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(1)).toBe("\tline 2"); - expect(editor.getCursorBufferPosition()).toEqual([1, 7]); - }); - }); - }); - - describe("when the snippet prefix is indented", () => { - describe("when the snippet spans a single line", () => { - it("does not indent the next line", () => { - editor.setCursorScreenPosition([2, Infinity]); - editor.insertText(' t1'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.lineTextForBufferRow(3)).toBe(" var pivot = items.shift(), current, left = [], right = [];"); - }); - }); - - describe("when the snippet spans multiple lines", () => { - it("indents the subsequent lines of the snippet to be even with the start of the first line", () => { - expect(editor.getSoftTabs()).toBeTruthy(); - editor.setCursorScreenPosition([2, Infinity]); - editor.insertText(' t3'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.lineTextForBufferRow(2)).toBe(" if (items.length <= 1) return items; line 1"); - expect(editor.lineTextForBufferRow(3)).toBe(" line 2"); - expect(editor.getCursorBufferPosition()).toEqual([3, 12]); - }); - }); - }); - - describe("when the snippet spans multiple lines", () => { + editor.setCursorScreenPosition([4, 0]) + editor.insertText('t2') + simulateTabKeyEvent() + simulateTabKeyEvent() + + editor.moveRight() + simulateTabKeyEvent({ shift: true }) + expect(editor.getCursorBufferPosition()).toEqual([4, 15]) + }) + }) + + describe('when the cursor is moved within the bounds of the current tab stop', () => { + it('should not terminate the snippet', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t8') + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder test') + editor.moveRight() + editor.moveLeft() + editor.insertText('foo') + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder tesfoot') + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder var quicksort = function () {') + editor.insertText('test') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder testvar quicksort = function () {') + editor.moveLeft() + editor.insertText('foo') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder tesfootvar quicksort = function () {') + }) + }) + + describe('when the backspace is press within the bounds of the current tab stop', () => { + it('should not terminate the snippet', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t8') + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder test') + editor.moveRight() + editor.backspace() + editor.insertText('foo') + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder tesfoo') + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder var quicksort = function () {') + editor.insertText('test') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder testvar quicksort = function () {') + editor.backspace() + editor.insertText('foo') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder tesfoovar quicksort = function () {') + }) + }) + }) + + describe('when the snippet contains hard tabs', () => { + describe('when the edit session is in soft-tabs mode', () => { + it('translates hard tabs in the snippet to the appropriate number of spaces', () => { + expect(editor.getSoftTabs()).toBeTruthy() + editor.insertText('t3') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(1)).toBe(' line 2') + expect(editor.getCursorBufferPosition()).toEqual([1, 8]) + }) + }) + + describe('when the edit session is in hard-tabs mode', () => { + it('inserts hard tabs in the snippet directly', () => { + editor.setSoftTabs(false) + editor.insertText('t3') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(1)).toBe('\tline 2') + expect(editor.getCursorBufferPosition()).toEqual([1, 7]) + }) + }) + }) + + describe('when the snippet prefix is indented', () => { + describe('when the snippet spans a single line', () => { + it('does not indent the next line', () => { + editor.setCursorScreenPosition([2, Infinity]) + editor.insertText(' t1') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.lineTextForBufferRow(3)).toBe(' var pivot = items.shift(), current, left = [], right = [];') + }) + }) + + describe('when the snippet spans multiple lines', () => { + it('indents the subsequent lines of the snippet to be even with the start of the first line', () => { + expect(editor.getSoftTabs()).toBeTruthy() + editor.setCursorScreenPosition([2, Infinity]) + editor.insertText(' t3') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.lineTextForBufferRow(2)).toBe(' if (items.length <= 1) return items; line 1') + expect(editor.lineTextForBufferRow(3)).toBe(' line 2') + expect(editor.getCursorBufferPosition()).toEqual([3, 12]) + }) + }) + }) + + describe('when the snippet spans multiple lines', () => { beforeEach(() => { - editor.update({autoIndent: true}); + editor.update({ autoIndent: true }) // editor.update() returns a Promise that never gets resolved, so we // need to return undefined to avoid a timeout in the spec. // TODO: Figure out why `editor.update({autoIndent: true})` never gets resolved. - }); - - it("places tab stops correctly", () => { - expect(editor.getSoftTabs()).toBeTruthy(); - editor.setCursorScreenPosition([2, Infinity]); - editor.insertText(' t3'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - expect(editor.getCursorBufferPosition()).toEqual([3, 12]); - atom.commands.dispatch(editorElement, 'snippets:next-tab-stop'); - expect(editor.getCursorBufferPosition()).toEqual([4, 4]); - }); - - it("indents the subsequent lines of the snippet based on the indent level before the snippet is inserted", () => { - editor.setCursorScreenPosition([2, Infinity]); - editor.insertNewline(); - editor.insertText('t4b'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - - expect(editor.lineTextForBufferRow(3)).toBe(" = line 1 {"); // 4 + 1 spaces (because the tab stop is invisible) - expect(editor.lineTextForBufferRow(4)).toBe(" line 2"); - expect(editor.lineTextForBufferRow(5)).toBe(" }"); - expect(editor.getCursorBufferPosition()).toEqual([3, 4]); - }); - - it("does not change the relative positioning of the tab stops when inserted multiple times", () => { - editor.setCursorScreenPosition([2, Infinity]); - editor.insertNewline(); - editor.insertText('t4'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - - expect(editor.getSelectedBufferRange()).toEqual([[3, 9], [3, 10]]); - atom.commands.dispatch(editorElement, 'snippets:next-tab-stop'); - expect(editor.getSelectedBufferRange()).toEqual([[4, 6], [4, 13]]); - - editor.insertText('t4'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - - expect(editor.getSelectedBufferRange()).toEqual([[4, 11], [4, 12]]); - atom.commands.dispatch(editorElement, 'snippets:next-tab-stop'); - expect(editor.getSelectedBufferRange()).toEqual([[5, 8], [5, 15]]); - - editor.setText(''); // Clear editor - editor.insertText('t4'); - atom.commands.dispatch(editorElement, 'snippets:expand'); - - expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 6]]); - atom.commands.dispatch(editorElement, 'snippets:next-tab-stop'); - expect(editor.getSelectedBufferRange()).toEqual([[1, 2], [1, 9]]); - }); - }); - - describe("when multiple snippets match the prefix", () => { - it("expands the snippet that is the longest match for the prefix", () => { - editor.insertText('t113'); - expect(editor.getCursorScreenPosition()).toEqual([0, 4]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("t113 var quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 6]); - - editor.undo(); - editor.undo(); - - editor.insertText("tt1"); - expect(editor.getCursorScreenPosition()).toEqual([0, 3]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("this is another testvar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 20]); - - editor.undo(); - editor.undo(); - - editor.insertText("@t1"); - expect(editor.getCursorScreenPosition()).toEqual([0, 3]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("@this is a testvar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 15]); - }); - }); - }); - - describe("when the word preceding the cursor ends with a snippet prefix", () => { - it("inserts a tab as normal", () => { - editor.insertText("t1t1t1"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("t1t1t1 var quicksort = function () {"); - }); - }); + }) + + it('places tab stops correctly', () => { + expect(editor.getSoftTabs()).toBeTruthy() + editor.setCursorScreenPosition([2, Infinity]) + editor.insertText(' t3') + atom.commands.dispatch(editorElement, 'snippets:expand') + expect(editor.getCursorBufferPosition()).toEqual([3, 12]) + atom.commands.dispatch(editorElement, 'snippets:next-tab-stop') + expect(editor.getCursorBufferPosition()).toEqual([4, 4]) + }) + + it('indents the subsequent lines of the snippet based on the indent level before the snippet is inserted', () => { + editor.setCursorScreenPosition([2, Infinity]) + editor.insertNewline() + editor.insertText('t4b') + atom.commands.dispatch(editorElement, 'snippets:expand') + + expect(editor.lineTextForBufferRow(3)).toBe(' = line 1 {') // 4 + 1 spaces (because the tab stop is invisible) + expect(editor.lineTextForBufferRow(4)).toBe(' line 2') + expect(editor.lineTextForBufferRow(5)).toBe(' }') + expect(editor.getCursorBufferPosition()).toEqual([3, 4]) + }) + + it('does not change the relative positioning of the tab stops when inserted multiple times', () => { + editor.setCursorScreenPosition([2, Infinity]) + editor.insertNewline() + editor.insertText('t4') + atom.commands.dispatch(editorElement, 'snippets:expand') + + expect(editor.getSelectedBufferRange()).toEqual([[3, 9], [3, 10]]) + atom.commands.dispatch(editorElement, 'snippets:next-tab-stop') + expect(editor.getSelectedBufferRange()).toEqual([[4, 6], [4, 13]]) + + editor.insertText('t4') + atom.commands.dispatch(editorElement, 'snippets:expand') + + expect(editor.getSelectedBufferRange()).toEqual([[4, 11], [4, 12]]) + atom.commands.dispatch(editorElement, 'snippets:next-tab-stop') + expect(editor.getSelectedBufferRange()).toEqual([[5, 8], [5, 15]]) + + editor.setText('') // Clear editor + editor.insertText('t4') + atom.commands.dispatch(editorElement, 'snippets:expand') + + expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 6]]) + atom.commands.dispatch(editorElement, 'snippets:next-tab-stop') + expect(editor.getSelectedBufferRange()).toEqual([[1, 2], [1, 9]]) + }) + }) + + describe('when multiple snippets match the prefix', () => { + it('expands the snippet that is the longest match for the prefix', () => { + editor.insertText('t113') + expect(editor.getCursorScreenPosition()).toEqual([0, 4]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('t113 var quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 6]) + + editor.undo() + editor.undo() + + editor.insertText('tt1') + expect(editor.getCursorScreenPosition()).toEqual([0, 3]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('this is another testvar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 20]) + + editor.undo() + editor.undo() + + editor.insertText('@t1') + expect(editor.getCursorScreenPosition()).toEqual([0, 3]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('@this is a testvar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 15]) + }) + }) + }) + + describe('when the word preceding the cursor ends with a snippet prefix', () => { + it('inserts a tab as normal', () => { + editor.insertText('t1t1t1') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('t1t1t1 var quicksort = function () {') + }) + }) describe("when the letters preceding the cursor don't match a snippet", () => { - it("inserts a tab as normal", () => { - editor.insertText("xxte"); - expect(editor.getCursorScreenPosition()).toEqual([0, 4]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("xxte var quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 6]); - }); - }); - - describe("when text is selected", () => { - it("inserts a tab as normal", () => { - editor.insertText("t1"); - editor.setSelectedBufferRange([[0, 0], [0, 2]]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe(" t1var quicksort = function () {"); - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 4]]); - }); - }); - - describe("when a previous snippet expansion has just been undone", () => { - describe("when the tab stops appear in the middle of the snippet", () => { + it('inserts a tab as normal', () => { + editor.insertText('xxte') + expect(editor.getCursorScreenPosition()).toEqual([0, 4]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('xxte var quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 6]) + }) + }) + + describe('when text is selected', () => { + it('inserts a tab as normal', () => { + editor.insertText('t1') + editor.setSelectedBufferRange([[0, 0], [0, 2]]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe(' t1var quicksort = function () {') + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 4]]) + }) + }) + + describe('when a previous snippet expansion has just been undone', () => { + describe('when the tab stops appear in the middle of the snippet', () => { it("expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", () => { - editor.insertText('t6\n'); - editor.setCursorBufferPosition([0, 2]); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("first line"); - editor.undo(); - expect(editor.lineTextForBufferRow(0)).toBe("t6"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("first line"); - }); - }); - - describe("when the tab stops appear at the beginning and then the end of snippet", () => { + editor.insertText('t6\n') + editor.setCursorBufferPosition([0, 2]) + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('first line') + editor.undo() + expect(editor.lineTextForBufferRow(0)).toBe('t6') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('first line') + }) + }) + + describe('when the tab stops appear at the beginning and then the end of snippet', () => { it("expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", () => { - editor.insertText('t6b\n'); - editor.setCursorBufferPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("expanded"); - editor.undo(); - expect(editor.lineTextForBufferRow(0)).toBe("t6b"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("expanded"); - expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - }); - }); - - describe("when the tab stops appear at the end and then the beginning of snippet", () => { + editor.insertText('t6b\n') + editor.setCursorBufferPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('expanded') + editor.undo() + expect(editor.lineTextForBufferRow(0)).toBe('t6b') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('expanded') + expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + }) + }) + + describe('when the tab stops appear at the end and then the beginning of snippet', () => { it("expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", () => { - editor.insertText('t6c\n'); - editor.setCursorBufferPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("expanded"); - editor.undo(); - expect(editor.lineTextForBufferRow(0)).toBe("t6c"); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("expanded"); - expect(editor.getCursorBufferPosition()).toEqual([0, 8]); - }); - }); - }); - - describe("when the prefix contains non-word characters", () => { - it("selects the non-word characters as part of the prefix", () => { - editor.insertText("@unique"); - expect(editor.getCursorScreenPosition()).toEqual([0, 7]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("@unique seevar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 11]); - - editor.setCursorBufferPosition([10, 0]); - editor.insertText("'@unique"); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(10)).toBe("'@unique see"); - expect(editor.getCursorScreenPosition()).toEqual([10, 12]); - }); - - it("does not select the whitespace before the prefix", () => { - editor.insertText("a; @unique"); - expect(editor.getCursorScreenPosition()).toEqual([0, 10]); - - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("a; @unique seevar quicksort = function () {"); - expect(editor.getCursorScreenPosition()).toEqual([0, 14]); - }); - }); - - describe("when snippet contains tabstops with or without placeholder", () => { - it("should create two markers", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t8'); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {"); - - expect(editor.getSelectedBufferRange()).toEqual([[0, 17], [0, 21]]); - - simulateTabKeyEvent(); - expect(editor.getSelectedBufferRange()).toEqual([[1, 20], [1, 20]]); - }); - }); - - describe("when snippet contains multi-caret tabstops with or without placeholder", () => { - it("should create two markers", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t9'); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {"); - editor.insertText('hello'); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder hello"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder hellovar quicksort = function () {"); - }); - - it("terminates the snippet when cursors are destroyed", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t9b'); - simulateTabKeyEvent(); - editor.getCursors()[0].destroy(); - editor.getCursorBufferPosition(); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(1)).toEqual("without placeholder "); - }); - - it("terminates the snippet expansion if a new cursor moves outside the bounds of the tab stops", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t9b'); - simulateTabKeyEvent(); - editor.insertText('test'); - - editor.getCursors()[0].destroy(); - editor.moveDown(); // this should destroy the previous expansion - editor.moveToBeginningOfLine(); + editor.insertText('t6c\n') + editor.setCursorBufferPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('expanded') + editor.undo() + expect(editor.lineTextForBufferRow(0)).toBe('t6c') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('expanded') + expect(editor.getCursorBufferPosition()).toEqual([0, 8]) + }) + }) + }) + + describe('when the prefix contains non-word characters', () => { + it('selects the non-word characters as part of the prefix', () => { + editor.insertText('@unique') + expect(editor.getCursorScreenPosition()).toEqual([0, 7]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('@unique seevar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 11]) + + editor.setCursorBufferPosition([10, 0]) + editor.insertText("'@unique") + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(10)).toBe("'@unique see") + expect(editor.getCursorScreenPosition()).toEqual([10, 12]) + }) + + it('does not select the whitespace before the prefix', () => { + editor.insertText('a; @unique') + expect(editor.getCursorScreenPosition()).toEqual([0, 10]) + + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('a; @unique seevar quicksort = function () {') + expect(editor.getCursorScreenPosition()).toEqual([0, 14]) + }) + }) + + describe('when snippet contains tabstops with or without placeholder', () => { + it('should create two markers', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t8') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder test') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder var quicksort = function () {') + + expect(editor.getSelectedBufferRange()).toEqual([[0, 17], [0, 21]]) + + simulateTabKeyEvent() + expect(editor.getSelectedBufferRange()).toEqual([[1, 20], [1, 20]]) + }) + }) + + describe('when snippet contains multi-caret tabstops with or without placeholder', () => { + it('should create two markers', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t9') + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder test') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder var quicksort = function () {') + editor.insertText('hello') + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder hello') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder hellovar quicksort = function () {') + }) + + it('terminates the snippet when cursors are destroyed', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t9b') + simulateTabKeyEvent() + editor.getCursors()[0].destroy() + editor.getCursorBufferPosition() + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(1)).toEqual('without placeholder ') + }) + + it('terminates the snippet expansion if a new cursor moves outside the bounds of the tab stops', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t9b') + simulateTabKeyEvent() + editor.insertText('test') + + editor.getCursors()[0].destroy() + editor.moveDown() // this should destroy the previous expansion + editor.moveToBeginningOfLine() // this should insert whitespace instead of going through tabstops of the previous destroyed snippet - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(2).indexOf(" second")).toBe(0); - }); + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(2).indexOf(' second')).toBe(0) + }) - it("moves to the second tabstop after a multi-caret tabstop", () => { - editor.setCursorScreenPosition([0, 0]); - editor.insertText('t9b'); - simulateTabKeyEvent(); - editor.insertText('line 1'); + it('moves to the second tabstop after a multi-caret tabstop', () => { + editor.setCursorScreenPosition([0, 0]) + editor.insertText('t9b') + simulateTabKeyEvent() + editor.insertText('line 1') - simulateTabKeyEvent(); - editor.insertText('line 2'); + simulateTabKeyEvent() + editor.insertText('line 2') - simulateTabKeyEvent(); - editor.insertText('line 3'); + simulateTabKeyEvent() + editor.insertText('line 3') - expect(editor.lineTextForBufferRow(2).indexOf("line 2 ")).toBe(-1); - }); + expect(editor.lineTextForBufferRow(2).indexOf('line 2 ')).toBe(-1) + }) it("mirrors input properly when a tabstop's placeholder refers to another tabstop", () => { - editor.setText('t17'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - editor.insertText("foo"); - expect(editor.getText()).toBe("console.log('uh foo', foo);"); - simulateTabKeyEvent(); - editor.insertText("bar"); - expect(editor.getText()).toBe("console.log('bar', foo);"); - }); - }); - - describe("when the snippet contains tab stops with transformations", () => { - it("transforms the text typed into the first tab stop before setting it in the transformed tab stop", () => { - editor.setText('t12'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.getText()).toBe("[b][/b]"); - editor.insertText('img src'); - expect(editor.getText()).toBe("[img src][/img]"); - }); - - it("bundles the transform mutations along with the original manual mutation for the purposes of undo and redo", () => { - editor.setText('t12'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - editor.insertText('i'); - expect(editor.getText()).toBe("[i][/i]"); - - editor.insertText('mg src'); - expect(editor.getText()).toBe("[img src][/img]"); - - editor.undo(); - expect(editor.getText()).toBe("[i][/i]"); - - editor.redo(); - expect(editor.getText()).toBe("[img src][/img]"); - }); - - it("can pick the right insertion to use as the primary even if a transformed insertion occurs first in the snippet", () => { - editor.setText('t16'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("& Q & q"); - expect(editor.getCursorBufferPosition()).toEqual([0, 7]); - - editor.insertText('rst'); - expect(editor.lineTextForBufferRow(0)).toBe("& RST & rst"); - }); - - it("silently ignores a tab stop without a non-transformed insertion to use as the primary", () => { - editor.setText('t15'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - editor.insertText('a'); - expect(editor.lineTextForBufferRow(0)).toBe(" & a"); - expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - }); - }); - - describe("when the snippet contains mirrored tab stops and tab stops with transformations", () => { - it("adds cursors for the mirrors but not the transformations", () => { - editor.setText('t13'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.getCursors().length).toBe(2); + editor.setText('t17') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + editor.insertText('foo') + expect(editor.getText()).toBe("console.log('uh foo', foo);") + simulateTabKeyEvent() + editor.insertText('bar') + expect(editor.getText()).toBe("console.log('bar', foo);") + }) + }) + + describe('when the snippet contains tab stops with transformations', () => { + it('transforms the text typed into the first tab stop before setting it in the transformed tab stop', () => { + editor.setText('t12') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.getText()).toBe('[b][/b]') + editor.insertText('img src') + expect(editor.getText()).toBe('[img src][/img]') + }) + + it('bundles the transform mutations along with the original manual mutation for the purposes of undo and redo', () => { + editor.setText('t12') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + editor.insertText('i') + expect(editor.getText()).toBe('[i][/i]') + + editor.insertText('mg src') + expect(editor.getText()).toBe('[img src][/img]') + + editor.undo() + expect(editor.getText()).toBe('[i][/i]') + + editor.redo() + expect(editor.getText()).toBe('[img src][/img]') + }) + + it('can pick the right insertion to use as the primary even if a transformed insertion occurs first in the snippet', () => { + editor.setText('t16') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('& Q & q') + expect(editor.getCursorBufferPosition()).toEqual([0, 7]) + + editor.insertText('rst') + expect(editor.lineTextForBufferRow(0)).toBe('& RST & rst') + }) + + it('silently ignores a tab stop without a non-transformed insertion to use as the primary', () => { + editor.setText('t15') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + editor.insertText('a') + expect(editor.lineTextForBufferRow(0)).toBe(' & a') + expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + }) + }) + + describe('when the snippet contains mirrored tab stops and tab stops with transformations', () => { + it('adds cursors for the mirrors but not the transformations', () => { + editor.setText('t13') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.getCursors().length).toBe(2) expect(editor.getText()).toBe(`\ placeholder PLACEHOLDER \ ` - ); + ) - editor.insertText('foo'); + editor.insertText('foo') expect(editor.getText()).toBe(`\ foo FOO foo\ ` - ); - }); - }); - - describe("when the snippet contains multiple tab stops, some with transformations and some without", () => { - it("does not get confused", () => { - editor.setText('t14'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.getCursors().length).toBe(2); - expect(editor.getText()).toBe("placeholder PLACEHOLDER ANOTHER another "); - simulateTabKeyEvent(); - expect(editor.getCursors().length).toBe(2); - editor.insertText('FOO'); - expect(editor.getText()).toBe("placeholder PLACEHOLDER FOO foo FOO"); - }); - }); - - describe("when the snippet has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step", () => { - it("terminates the snippet upon such a cursor move", () => { - editor.setText('t18'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.getText()).toBe("// \n// "); - expect(editor.getCursorBufferPosition()).toEqual([0, 3]); - editor.insertText('wat'); - expect(editor.getText()).toBe("// wat\n// ==="); + ) + }) + }) + + describe('when the snippet contains multiple tab stops, some with transformations and some without', () => { + it('does not get confused', () => { + editor.setText('t14') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.getCursors().length).toBe(2) + expect(editor.getText()).toBe('placeholder PLACEHOLDER ANOTHER another ') + simulateTabKeyEvent() + expect(editor.getCursors().length).toBe(2) + editor.insertText('FOO') + expect(editor.getText()).toBe('placeholder PLACEHOLDER FOO foo FOO') + }) + }) + + describe('when the snippet has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step', () => { + it('terminates the snippet upon such a cursor move', () => { + editor.setText('t18') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.getText()).toBe('// \n// ') + expect(editor.getCursorBufferPosition()).toEqual([0, 3]) + editor.insertText('wat') + expect(editor.getText()).toBe('// wat\n// ===') // Move the cursor down one line, then up one line. This puts the cursor // back in its previous position, but the snippet should no longer be // active, so when we type more text, it should not be mirrored. - editor.setCursorScreenPosition([1, 6]); - editor.setCursorScreenPosition([0, 6]); - editor.insertText('wat'); - expect(editor.getText()).toBe("// watwat\n// ==="); - }); - }); - - describe("when the snippet contains tab stops with an index >= 10", () => { - it("parses and orders the indices correctly", () => { - editor.setText('t10'); - editor.setCursorScreenPosition([0, 3]); - simulateTabKeyEvent(); - expect(editor.getText()).toBe("hello large indices"); - expect(editor.getCursorBufferPosition()).toEqual([0, 19]); - simulateTabKeyEvent(); - expect(editor.getCursorBufferPosition()).toEqual([0, 5]); - simulateTabKeyEvent(); - expect(editor.getSelectedBufferRange()).toEqual([[0, 6], [0, 11]]); - }); - }); - - describe("when there are multiple cursors", () => { - describe("when the cursors share a common snippet prefix", () => { - it("expands the snippet for all cursors and allows simultaneous editing", () => { - editor.insertText('t9'); - editor.setCursorBufferPosition([12, 2]); - editor.insertText(' t9'); - editor.addCursorAtBufferPosition([0, 2]); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {"); - expect(editor.lineTextForBufferRow(13)).toBe("}; with placeholder test"); - expect(editor.lineTextForBufferRow(14)).toBe("without placeholder "); - - editor.insertText('hello'); - expect(editor.lineTextForBufferRow(0)).toBe("with placeholder hello"); - expect(editor.lineTextForBufferRow(1)).toBe("without placeholder hellovar quicksort = function () {"); - expect(editor.lineTextForBufferRow(13)).toBe("}; with placeholder hello"); - expect(editor.lineTextForBufferRow(14)).toBe("without placeholder hello"); - }); - - it("applies transformations identically to single-expansion mode", () => { - editor.setText('t14\nt14'); - editor.setCursorBufferPosition([1, 3]); - editor.addCursorAtBufferPosition([0, 3]); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another "); - - editor.insertText("testing"); - - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another "); - - simulateTabKeyEvent(); - editor.insertText("AGAIN"); - - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN"); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN"); - }); - - it("bundles transform-induced mutations into a single history entry along with their triggering edit, even across multiple snippets", () => { - editor.setText('t14\nt14'); - editor.setCursorBufferPosition([1, 3]); - editor.addCursorAtBufferPosition([0, 3]); - simulateTabKeyEvent(); - - expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another "); - - editor.insertText("testing"); - - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another "); - - simulateTabKeyEvent(); - editor.insertText("AGAIN"); - - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN"); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN"); - - editor.undo(); - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another "); - - editor.undo(); - expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another "); - - editor.redo(); - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another "); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another "); - - editor.redo(); - expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN"); - expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN"); - }); - - describe("when there are many tabstops", () => { - it("moves the cursors between the tab stops for their corresponding snippet when tab and shift-tab are pressed", () => { - editor.addCursorAtBufferPosition([7, 5]); - editor.addCursorAtBufferPosition([12, 2]); - editor.insertText('t11'); - simulateTabKeyEvent(); - - const cursors = editor.getCursors(); - expect(cursors.length).toEqual(3); - - expect(cursors[0].getBufferPosition()).toEqual([0, 3]); - expect(cursors[1].getBufferPosition()).toEqual([7, 8]); - expect(cursors[2].getBufferPosition()).toEqual([12, 5]); - expect(cursors[0].selection.isEmpty()).toBe(true); - expect(cursors[1].selection.isEmpty()).toBe(true); - expect(cursors[2].selection.isEmpty()).toBe(true); - - simulateTabKeyEvent(); - expect(cursors[0].getBufferPosition()).toEqual([0, 7]); - expect(cursors[1].getBufferPosition()).toEqual([7, 12]); - expect(cursors[2].getBufferPosition()).toEqual([12, 9]); - expect(cursors[0].selection.isEmpty()).toBe(false); - expect(cursors[1].selection.isEmpty()).toBe(false); - expect(cursors[2].selection.isEmpty()).toBe(false); - expect(cursors[0].selection.getText()).toEqual('two'); - expect(cursors[1].selection.getText()).toEqual('two'); - expect(cursors[2].selection.getText()).toEqual('two'); - - simulateTabKeyEvent(); - expect(cursors[0].getBufferPosition()).toEqual([0, 13]); - expect(cursors[1].getBufferPosition()).toEqual([7, 18]); - expect(cursors[2].getBufferPosition()).toEqual([12, 15]); - expect(cursors[0].selection.isEmpty()).toBe(true); - expect(cursors[1].selection.isEmpty()).toBe(true); - expect(cursors[2].selection.isEmpty()).toBe(true); - - simulateTabKeyEvent(); - expect(cursors[0].getBufferPosition()).toEqual([0, 0]); - expect(cursors[1].getBufferPosition()).toEqual([7, 5]); - expect(cursors[2].getBufferPosition()).toEqual([12, 2]); - expect(cursors[0].selection.isEmpty()).toBe(true); - expect(cursors[1].selection.isEmpty()).toBe(true); - expect(cursors[2].selection.isEmpty()).toBe(true); - }); - }); - }); - - describe("when the cursors do not share common snippet prefixes", () => { - it("inserts tabs as normal", () => { - editor.insertText('t9'); - editor.setCursorBufferPosition([12, 2]); - editor.insertText(' t8'); - editor.addCursorAtBufferPosition([0, 2]); - simulateTabKeyEvent(); - expect(editor.lineTextForBufferRow(0)).toBe("t9 var quicksort = function () {"); - expect(editor.lineTextForBufferRow(12)).toBe("}; t8 "); - }); - }); - - describe("when a snippet is triggered within an existing snippet expansion", () => { - it("ignores the snippet expansion and goes to the next tab stop", () => { - editor.addCursorAtBufferPosition([7, 5]); - editor.addCursorAtBufferPosition([12, 2]); - editor.insertText('t11'); - simulateTabKeyEvent(); - simulateTabKeyEvent(); - - editor.insertText('t1'); - simulateTabKeyEvent(); - - const cursors = editor.getCursors(); - expect(cursors.length).toEqual(3); - - expect(cursors[0].getBufferPosition()).toEqual([0, 12]); - expect(cursors[1].getBufferPosition()).toEqual([7, 17]); - expect(cursors[2].getBufferPosition()).toEqual([12, 14]); - expect(cursors[0].selection.isEmpty()).toBe(true); - expect(cursors[1].selection.isEmpty()).toBe(true); - expect(cursors[2].selection.isEmpty()).toBe(true); - expect(editor.lineTextForBufferRow(0)).toBe("one t1 threevar quicksort = function () {"); - expect(editor.lineTextForBufferRow(7)).toBe(" }one t1 three"); - expect(editor.lineTextForBufferRow(12)).toBe("};one t1 three"); - }); - }); - }); - - describe("when the editor is not a pane item (regression)", () => { - it("handles tab stops correctly", () => { - editor = new TextEditor(); - atom.grammars.assignLanguageMode(editor, 'source.js'); - editorElement = editor.getElement(); - - editor.insertText('t2'); - simulateTabKeyEvent(); - editor.insertText('ABC'); - expect(editor.getText()).toContain('go here first:(ABC)'); - - editor.undo(); - editor.undo(); - expect(editor.getText()).toBe('t2'); - simulateTabKeyEvent(); - editor.insertText('ABC'); - expect(editor.getText()).toContain('go here first:(ABC)'); - }); - }); - }); - - describe("when atom://.atom/snippets is opened", () => { - it("opens ~/.atom/snippets.cson", () => { - jasmine.unspy(Snippets, 'getUserSnippetsPath'); - atom.workspace.destroyActivePaneItem(); - const configDirPath = temp.mkdirSync('atom-config-dir-'); - spyOn(atom, 'getConfigDirPath').andReturn(configDirPath); - atom.workspace.open('atom://.atom/snippets'); - - waitsFor(() => atom.workspace.getActiveTextEditor() != null); + editor.setCursorScreenPosition([1, 6]) + editor.setCursorScreenPosition([0, 6]) + editor.insertText('wat') + expect(editor.getText()).toBe('// watwat\n// ===') + }) + }) + + describe('when the snippet contains tab stops with an index >= 10', () => { + it('parses and orders the indices correctly', () => { + editor.setText('t10') + editor.setCursorScreenPosition([0, 3]) + simulateTabKeyEvent() + expect(editor.getText()).toBe('hello large indices') + expect(editor.getCursorBufferPosition()).toEqual([0, 19]) + simulateTabKeyEvent() + expect(editor.getCursorBufferPosition()).toEqual([0, 5]) + simulateTabKeyEvent() + expect(editor.getSelectedBufferRange()).toEqual([[0, 6], [0, 11]]) + }) + }) + + describe('when there are multiple cursors', () => { + describe('when the cursors share a common snippet prefix', () => { + it('expands the snippet for all cursors and allows simultaneous editing', () => { + editor.insertText('t9') + editor.setCursorBufferPosition([12, 2]) + editor.insertText(' t9') + editor.addCursorAtBufferPosition([0, 2]) + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder test') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder var quicksort = function () {') + expect(editor.lineTextForBufferRow(13)).toBe('}; with placeholder test') + expect(editor.lineTextForBufferRow(14)).toBe('without placeholder ') + + editor.insertText('hello') + expect(editor.lineTextForBufferRow(0)).toBe('with placeholder hello') + expect(editor.lineTextForBufferRow(1)).toBe('without placeholder hellovar quicksort = function () {') + expect(editor.lineTextForBufferRow(13)).toBe('}; with placeholder hello') + expect(editor.lineTextForBufferRow(14)).toBe('without placeholder hello') + }) + + it('applies transformations identically to single-expansion mode', () => { + editor.setText('t14\nt14') + editor.setCursorBufferPosition([1, 3]) + editor.addCursorAtBufferPosition([0, 3]) + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('placeholder PLACEHOLDER ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('placeholder PLACEHOLDER ANOTHER another ') + + editor.insertText('testing') + + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing ANOTHER another ') + + simulateTabKeyEvent() + editor.insertText('AGAIN') + + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing AGAIN again AGAIN') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing AGAIN again AGAIN') + }) + + it('bundles transform-induced mutations into a single history entry along with their triggering edit, even across multiple snippets', () => { + editor.setText('t14\nt14') + editor.setCursorBufferPosition([1, 3]) + editor.addCursorAtBufferPosition([0, 3]) + simulateTabKeyEvent() + + expect(editor.lineTextForBufferRow(0)).toBe('placeholder PLACEHOLDER ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('placeholder PLACEHOLDER ANOTHER another ') + + editor.insertText('testing') + + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing ANOTHER another ') + + simulateTabKeyEvent() + editor.insertText('AGAIN') + + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing AGAIN again AGAIN') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing AGAIN again AGAIN') + + editor.undo() + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing ANOTHER another ') + + editor.undo() + expect(editor.lineTextForBufferRow(0)).toBe('placeholder PLACEHOLDER ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('placeholder PLACEHOLDER ANOTHER another ') + + editor.redo() + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing ANOTHER another ') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing ANOTHER another ') + + editor.redo() + expect(editor.lineTextForBufferRow(0)).toBe('testing TESTING testing AGAIN again AGAIN') + expect(editor.lineTextForBufferRow(1)).toBe('testing TESTING testing AGAIN again AGAIN') + }) + + describe('when there are many tabstops', () => { + it('moves the cursors between the tab stops for their corresponding snippet when tab and shift-tab are pressed', () => { + editor.addCursorAtBufferPosition([7, 5]) + editor.addCursorAtBufferPosition([12, 2]) + editor.insertText('t11') + simulateTabKeyEvent() + + const cursors = editor.getCursors() + expect(cursors.length).toEqual(3) + + expect(cursors[0].getBufferPosition()).toEqual([0, 3]) + expect(cursors[1].getBufferPosition()).toEqual([7, 8]) + expect(cursors[2].getBufferPosition()).toEqual([12, 5]) + expect(cursors[0].selection.isEmpty()).toBe(true) + expect(cursors[1].selection.isEmpty()).toBe(true) + expect(cursors[2].selection.isEmpty()).toBe(true) + + simulateTabKeyEvent() + expect(cursors[0].getBufferPosition()).toEqual([0, 7]) + expect(cursors[1].getBufferPosition()).toEqual([7, 12]) + expect(cursors[2].getBufferPosition()).toEqual([12, 9]) + expect(cursors[0].selection.isEmpty()).toBe(false) + expect(cursors[1].selection.isEmpty()).toBe(false) + expect(cursors[2].selection.isEmpty()).toBe(false) + expect(cursors[0].selection.getText()).toEqual('two') + expect(cursors[1].selection.getText()).toEqual('two') + expect(cursors[2].selection.getText()).toEqual('two') + + simulateTabKeyEvent() + expect(cursors[0].getBufferPosition()).toEqual([0, 13]) + expect(cursors[1].getBufferPosition()).toEqual([7, 18]) + expect(cursors[2].getBufferPosition()).toEqual([12, 15]) + expect(cursors[0].selection.isEmpty()).toBe(true) + expect(cursors[1].selection.isEmpty()).toBe(true) + expect(cursors[2].selection.isEmpty()).toBe(true) + + simulateTabKeyEvent() + expect(cursors[0].getBufferPosition()).toEqual([0, 0]) + expect(cursors[1].getBufferPosition()).toEqual([7, 5]) + expect(cursors[2].getBufferPosition()).toEqual([12, 2]) + expect(cursors[0].selection.isEmpty()).toBe(true) + expect(cursors[1].selection.isEmpty()).toBe(true) + expect(cursors[2].selection.isEmpty()).toBe(true) + }) + }) + }) + + describe('when the cursors do not share common snippet prefixes', () => { + it('inserts tabs as normal', () => { + editor.insertText('t9') + editor.setCursorBufferPosition([12, 2]) + editor.insertText(' t8') + editor.addCursorAtBufferPosition([0, 2]) + simulateTabKeyEvent() + expect(editor.lineTextForBufferRow(0)).toBe('t9 var quicksort = function () {') + expect(editor.lineTextForBufferRow(12)).toBe('}; t8 ') + }) + }) + + describe('when a snippet is triggered within an existing snippet expansion', () => { + it('ignores the snippet expansion and goes to the next tab stop', () => { + editor.addCursorAtBufferPosition([7, 5]) + editor.addCursorAtBufferPosition([12, 2]) + editor.insertText('t11') + simulateTabKeyEvent() + simulateTabKeyEvent() + + editor.insertText('t1') + simulateTabKeyEvent() + + const cursors = editor.getCursors() + expect(cursors.length).toEqual(3) + + expect(cursors[0].getBufferPosition()).toEqual([0, 12]) + expect(cursors[1].getBufferPosition()).toEqual([7, 17]) + expect(cursors[2].getBufferPosition()).toEqual([12, 14]) + expect(cursors[0].selection.isEmpty()).toBe(true) + expect(cursors[1].selection.isEmpty()).toBe(true) + expect(cursors[2].selection.isEmpty()).toBe(true) + expect(editor.lineTextForBufferRow(0)).toBe('one t1 threevar quicksort = function () {') + expect(editor.lineTextForBufferRow(7)).toBe(' }one t1 three') + expect(editor.lineTextForBufferRow(12)).toBe('};one t1 three') + }) + }) + }) + + describe('when the editor is not a pane item (regression)', () => { + it('handles tab stops correctly', () => { + editor = new TextEditor() + atom.grammars.assignLanguageMode(editor, 'source.js') + editorElement = editor.getElement() + + editor.insertText('t2') + simulateTabKeyEvent() + editor.insertText('ABC') + expect(editor.getText()).toContain('go here first:(ABC)') + + editor.undo() + editor.undo() + expect(editor.getText()).toBe('t2') + simulateTabKeyEvent() + editor.insertText('ABC') + expect(editor.getText()).toContain('go here first:(ABC)') + }) + }) + }) + + describe('when atom://.atom/snippets is opened', () => { + it('opens ~/.atom/snippets.cson', () => { + jasmine.unspy(Snippets, 'getUserSnippetsPath') + atom.workspace.destroyActivePaneItem() + const configDirPath = temp.mkdirSync('atom-config-dir-') + spyOn(atom, 'getConfigDirPath').andReturn(configDirPath) + atom.workspace.open('atom://.atom/snippets') + + waitsFor(() => atom.workspace.getActiveTextEditor() != null) runs(() => { - expect(atom.workspace.getActiveTextEditor().getURI()).toBe(path.join(configDirPath, 'snippets.cson')); - }); - }); - }); + expect(atom.workspace.getActiveTextEditor().getURI()).toBe(path.join(configDirPath, 'snippets.cson')) + }) + }) + }) - describe("snippet insertion API", () => { - it("will automatically parse snippet definition and replace selection", () => { - editor.setSelectedBufferRange([[0, 4], [0, 13]]); - Snippets.insert("hello ${1:world}", editor); + describe('snippet insertion API', () => { + it('will automatically parse snippet definition and replace selection', () => { + editor.setSelectedBufferRange([[0, 4], [0, 13]]) + Snippets.insert('hello ${1:world}', editor) - expect(editor.lineTextForBufferRow(0)).toBe("var hello world = function () {"); - expect(editor.getSelectedBufferRange()).toEqual([[0, 10], [0, 15]]); - }); - }); + expect(editor.lineTextForBufferRow(0)).toBe('var hello world = function () {') + expect(editor.getSelectedBufferRange()).toEqual([[0, 10], [0, 15]]) + }) + }) describe("when the 'snippets:available' command is triggered", () => { - let availableSnippetsView = null; + let availableSnippetsView = null beforeEach(() => { Snippets.add(__filename, { - ".source.js": { - "test": { - prefix: "test", - body: "${1:Test pass you will}, young " + '.source.js': { + test: { + prefix: 'test', + body: '${1:Test pass you will}, young ' }, - "challenge": { - prefix: "chal", - body: "$1: ${2:To pass this challenge}" + challenge: { + prefix: 'chal', + body: '$1: ${2:To pass this challenge}' } } } - ); + ) - delete Snippets.availableSnippetsView; + delete Snippets.availableSnippetsView - atom.commands.dispatch(editorElement, "snippets:available"); + atom.commands.dispatch(editorElement, 'snippets:available') - waitsFor(() => atom.workspace.getModalPanels().length === 1); + waitsFor(() => atom.workspace.getModalPanels().length === 1) runs(() => { - availableSnippetsView = atom.workspace.getModalPanels()[0].getItem(); - }); - }); - - it("renders a select list of all available snippets", () => { - expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('test'); - expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('test'); - expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('${1:Test pass you will}, young '); - - availableSnippetsView.selectListView.selectNext(); - - expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('chal'); - expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('challenge'); - expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('$1: ${2:To pass this challenge}'); - }); - - it("writes the selected snippet to the editor as snippet", () => { - availableSnippetsView.selectListView.confirmSelection(); - - expect(editor.getCursorScreenPosition()).toEqual([0, 18]); - expect(editor.getSelectedText()).toBe('Test pass you will'); - expect(editor.lineTextForBufferRow(0)).toBe('Test pass you will, young var quicksort = function () {'); - }); - - it("closes the dialog when triggered again", () => { - atom.commands.dispatch(availableSnippetsView.selectListView.refs.queryEditor.element, 'snippets:available'); - expect(atom.workspace.getModalPanels().length).toBe(0); - }); - }); -}); + availableSnippetsView = atom.workspace.getModalPanels()[0].getItem() + }) + }) + + it('renders a select list of all available snippets', () => { + expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('test') + expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('test') + expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('${1:Test pass you will}, young ') + + availableSnippetsView.selectListView.selectNext() + + expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('chal') + expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('challenge') + expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('$1: ${2:To pass this challenge}') + }) + + it('writes the selected snippet to the editor as snippet', () => { + availableSnippetsView.selectListView.confirmSelection() + + expect(editor.getCursorScreenPosition()).toEqual([0, 18]) + expect(editor.getSelectedText()).toBe('Test pass you will') + expect(editor.lineTextForBufferRow(0)).toBe('Test pass you will, young var quicksort = function () {') + }) + + it('closes the dialog when triggered again', () => { + atom.commands.dispatch(availableSnippetsView.selectListView.refs.queryEditor.element, 'snippets:available') + expect(atom.workspace.getModalPanels().length).toBe(0) + }) + }) +}) diff --git a/specs/snippets-spec.js b/specs/snippets-spec.js new file mode 100644 index 00000000..e85411ea --- /dev/null +++ b/specs/snippets-spec.js @@ -0,0 +1,64 @@ +const mock = require('mock-fs'); + +const Snippets = require('../snippets') + +describe('Snippets', () => { + // userSnippetsPath + // loadSnippetsFile() + // loadUserSnippets() + // loadPackage() + // unloadPackage() + // + // activate + // deactivate + + // snippets + describe('snippets API', () => { + const api = Snippets.snippets() + /* + userSnippetsPath: () => this.userSnippetsPath, + snippetsByScopes: () => this.snippetsByScopes, + */ + + describe('userSnippetsPath', () => { + waitsForPromise(() => atom.workspace.open('sample.js')) + + const editor = atom.workspace.getActiveTextEditor() + + it('returns a `Snippet`', () => { + const snippet = api.parse('this is a snippet') + + expect(snippet).toBeInstanceOf(Snippet) + }) + }) + + describe('loaded', () => { + describe('before activation', () => { + it('should resolve to false', () => { + expect(api.loaded).toBeInstanceOf(Promise) + waitsForPromise(() => api.loaded.then(result => expect(result).toBe(false))) + }) + }) + + waitsForPromise(() => atom.packages.activatePackage('snippets-dev')) + + describe('after activation', () => { + it('should resolve to true', () => { + expect(api.loaded).toBeInstanceOf(Promise) + waitsForPromise(() => api.loaded.then(result => expect(result).toBe(true))) + }) + }) + + waitsForPromise(() => atom.packages.deactivatePackage('snippets-dev')) + + describe('after deactivation', () => { + it('should resolve to false', () => { + expect(api.loaded).toBeInstanceOf(Promise) + waitsForPromise(() => api.loaded.then(result => expect(result).toBe(false))) + }) + }) + }) + + describe('') + }) +})