From c58b8f7a22268d6972deac08b498c7f80ba175a3 Mon Sep 17 00:00:00 2001 From: David Evans Date: Sun, 22 Apr 2018 14:15:51 +0100 Subject: [PATCH] Linter fixes --- lib/sequence-diagram-web.js | 827 +++++++++--------- lib/sequence-diagram-web.min.js | 2 +- lib/sequence-diagram.js | 827 +++++++++--------- lib/sequence-diagram.min.js | 2 +- scripts/sequence/codemirror/Mode.mjs | 189 ++-- scripts/sequence/codemirror/Mode_webspec.mjs | 350 ++++---- scripts/sequence/codemirror/hints.mjs | 41 +- scripts/sequence/generator/Generator.mjs | 177 ++-- .../sequence/parser/MarkdownParser_spec.mjs | 80 +- scripts/sequence/parser/Parser.mjs | 201 +++-- scripts/sequence/parser/Parser_spec.mjs | 26 + scripts/sequence/renderer/Renderer.mjs | 2 - scripts/sequence/renderer/Renderer_spec.mjs | 196 ++--- .../sequence/renderer/components/Connect.mjs | 54 +- scripts/sequence/renderer/components/Note.mjs | 119 ++- scripts/sequence/themes/BaseTheme.mjs | 50 +- spec/image/ImageRegion.mjs | 53 +- spec/support/eslintrc.js | 2 +- web/interface/Interface.mjs | 2 - weblib/editor.js | 2 - 20 files changed, 1641 insertions(+), 1561 deletions(-) diff --git a/lib/sequence-diagram-web.js b/lib/sequence-diagram-web.js index 1b8b257..66b850a 100644 --- a/lib/sequence-diagram-web.js +++ b/lib/sequence-diagram-web.js @@ -1,8 +1,6 @@ (function () { 'use strict'; - /* eslint-disable sort-keys */ // Maybe later - function optionsAttributes(attributes, options) { const attrs = Object.assign({}, attributes['']); options.forEach((opt) => { @@ -66,14 +64,14 @@ return optionsAttributes(attributes, options); } - renderAgentLine({x, y0, y1, width, className, options}) { + renderAgentLine({className, options, width, x, y0, y1}) { const attrs = this.optionsAttributes(this.agentLineAttrs, options); if(width > 0) { return this.svg.box(attrs, { + height: y1 - y0, + width, x: x - width / 2, y: y0, - width, - height: y1 - y0, }).addClass(className); } else { return this.svg.line(attrs, { @@ -87,7 +85,7 @@ // INTERNAL HELPERS - renderArrowHead(attrs, {x, y, width, height, dir}) { + renderArrowHead(attrs, {dir, height, width, x, y}) { const wx = width * dir.dx; const wy = width * dir.dy; const hy = height * 0.5 * dir.dx; @@ -101,7 +99,7 @@ .attrs(attrs); } - renderTag(attrs, {x, y, width, height}) { + renderTag(attrs, {height, width, x, y}) { const {rx, ry} = attrs; const x2 = x + width; const y2 = y + height; @@ -151,12 +149,12 @@ renderRef(options, position) { return { - shape: this.svg.box(options, position).attrs({'fill': 'none'}), + fill: this.svg.box(options, position).attrs({'stroke': 'none'}), mask: this.svg.box(options, position).attrs({ 'fill': '#000000', 'stroke': 'none', }), - fill: this.svg.box(options, position).attrs({'stroke': 'none'}), + shape: this.svg.box(options, position).attrs({'fill': 'none'}), }; } @@ -166,6 +164,8 @@ {x1, y1, x2, y2} ) { return { + p1: {x: x1, y: y1}, + p2: {x: x2, y: y2}, shape: this.svg.el('path') .attr('d', this.svg.patternedLine(pattern) .move(x1, y1) @@ -173,15 +173,13 @@ .cap() .asPath()) .attrs(attrs), - p1: {x: x1, y: y1}, - p2: {x: x2, y: y2}, }; } renderRevConnect( pattern, attrs, - {x1, y1, x2, y2, xR, rad} + {rad, x1, x2, xR, y1, y2} ) { const maxRad = (y2 - y1) / 2; const line = this.svg.patternedLine(pattern) @@ -196,20 +194,20 @@ line.arc(xR, (y1 + y2) / 2, Math.PI); } return { + p1: {x: x1, y: y1}, + p2: {x: x2, y: y2}, shape: this.svg.el('path') .attr('d', line .line(x2, y2) .cap() .asPath()) .attrs(attrs), - p1: {x: x1, y: y1}, - p2: {x: x2, y: y2}, }; } renderLineDivider( {lineAttrs}, - {x, y, labelWidth, width, height} + {height, labelWidth, width, x, y} ) { let shape = null; const yPos = y + height / 2; @@ -241,25 +239,25 @@ renderDelayDivider( {dotSize, gapSize}, - {x, y, width, height} + {height, width, x, y} ) { const mask = this.svg.el('g'); for(let i = 0; i + gapSize <= height; i += dotSize + gapSize) { mask.add(this.svg.box({ 'fill': '#000000', }, { + height: gapSize, + width, x, y: y + i, - width, - height: gapSize, })); } return {mask}; } renderTearDivider( - {fadeBegin, fadeSize, pattern, zigWidth, zigHeight, lineAttrs}, - {x, y, labelWidth, labelHeight, width, height, env} + {fadeBegin, fadeSize, lineAttrs, pattern, zigHeight, zigWidth}, + {env, height, labelHeight, labelWidth, width, x, y} ) { const maskGradID = env.addDef('tear-grad', () => { const px = 100 / width; @@ -288,24 +286,24 @@ this.svg.box({ 'fill': 'url(#' + maskGradID + ')', }, { + height: height + 10, + width, x, y: y - 5, - width, - height: height + 10, }) ); const shapeMaskID = env.addDef(shapeMask); if(labelWidth > 0) { shapeMask.add(this.svg.box({ + 'fill': '#000000', 'rx': 2, 'ry': 2, - 'fill': '#000000', }, { + 'height': labelHeight + 2, + 'width': labelWidth, 'x': x + (width - labelWidth) / 2, 'y': y + (height - labelHeight) / 2 - 1, - 'width': labelWidth, - 'height': labelHeight + 2, })); } @@ -348,7 +346,7 @@ 'fill': '#000000', }); } - return {shape, mask}; + return {mask, shape}; } } @@ -1492,15 +1490,6 @@ // Agent from Generator: {id, formattedLabel, anchorRight} const GAgent = { - equals: (a, b) => (a.id === b.id), - make: (id, {anchorRight = false, isVirtualSource = false} = {}) => ({ - anchorRight, - id, - isVirtualSource, - options: [], - }), - indexOf: (list, gAgent) => indexOf(list, gAgent, GAgent.equals), - hasIntersection: (a, b) => hasIntersection(a, b, GAgent.equals), addNearby: (target, reference, item, offset) => { const p = indexOf(target, reference, GAgent.equals); if(p === -1) { @@ -1509,11 +1498,28 @@ target.splice(p + offset, 0, item); } }, + equals: (a, b) => (a.id === b.id), + hasIntersection: (a, b) => hasIntersection(a, b, GAgent.equals), + indexOf: (list, gAgent) => indexOf(list, gAgent, GAgent.equals), + make: (id, {anchorRight = false, isVirtualSource = false} = {}) => ({ + anchorRight, + id, + isVirtualSource, + options: [], + }), }; + function isExpiredGroupAlias(state) { + return state.blocked && state.group === null; + } + + function isReservedAgentName(name) { + return name.startsWith('__'); + } + const NOTE_DEFAULT_G_AGENTS = { - 'note over': [GAgent.make('['), GAgent.make(']')], 'note left': [GAgent.make('[')], + 'note over': [GAgent.make('['), GAgent.make(']')], 'note right': [GAgent.make(']')], }; @@ -1698,25 +1704,25 @@ this.currentNest = null; this.stageHandlers = { - 'block begin': this.handleBlockBegin.bind(this), - 'block split': this.handleBlockSplit.bind(this), - 'block end': this.handleBlockEnd.bind(this), - 'group begin': this.handleGroupBegin.bind(this), - 'mark': this.handleMark.bind(this), - 'async': this.handleAsync.bind(this), - 'agent define': this.handleAgentDefine.bind(this), - 'agent options': this.handleAgentOptions.bind(this), 'agent begin': this.handleAgentBegin.bind(this), + 'agent define': this.handleAgentDefine.bind(this), 'agent end': this.handleAgentEnd.bind(this), - 'divider': this.handleDivider.bind(this), - 'label pattern': this.handleLabelPattern.bind(this), + 'agent options': this.handleAgentOptions.bind(this), + 'async': this.handleAsync.bind(this), + 'block begin': this.handleBlockBegin.bind(this), + 'block end': this.handleBlockEnd.bind(this), + 'block split': this.handleBlockSplit.bind(this), 'connect': this.handleConnect.bind(this), 'connect-delay-begin': this.handleConnectDelayBegin.bind(this), 'connect-delay-end': this.handleConnectDelayEnd.bind(this), - 'note over': this.handleNote.bind(this), - 'note left': this.handleNote.bind(this), - 'note right': this.handleNote.bind(this), + 'divider': this.handleDivider.bind(this), + 'group begin': this.handleGroupBegin.bind(this), + 'label pattern': this.handleLabelPattern.bind(this), + 'mark': this.handleMark.bind(this), 'note between': this.handleNote.bind(this), + 'note left': this.handleNote.bind(this), + 'note over': this.handleNote.bind(this), + 'note right': this.handleNote.bind(this), }; this.expandGroupedGAgent = this.expandGroupedGAgent.bind(this); this.handleStage = this.handleStage.bind(this); @@ -1724,6 +1730,14 @@ this.endGroup = this.endGroup.bind(this); } + _aliasInUse(alias) { + const old = this.agentAliases.get(alias); + if(old && old !== alias) { + return true; + } + return this.gAgents.some((gAgent) => (gAgent.id === alias)); + } + toGAgent({name, alias, flags}) { if(alias) { if(this.agentAliases.has(name)) { @@ -1731,11 +1745,7 @@ 'Cannot alias ' + name + '; it is already an alias' ); } - const old = this.agentAliases.get(alias); - if( - (old && old !== alias) || - this.gAgents.some((gAgent) => (gAgent.id === alias)) - ) { + if(this._aliasInUse(alias)) { throw new Error( 'Cannot use ' + alias + ' as an alias; it is already in use' @@ -1776,8 +1786,8 @@ } }); this.addStage({ - type: 'parallel', stages: viableStages, + type: 'parallel', }); } @@ -1812,25 +1822,27 @@ allowCovered = false, allowVirtual = false, } = {}) { + /* eslint-disable complexity */ // The checks are quite simple gAgents.forEach((gAgent) => { + /* eslint-enable complexity */ const state = this.getGAgentState(gAgent); - if(state.blocked && state.group === null) { + const name = gAgent.id; + + if(isExpiredGroupAlias(state)) { // Used to be a group alias; can never be reused - throw new Error('Duplicate agent name: ' + gAgent.id); + throw new Error('Duplicate agent name: ' + name); } if(!allowCovered && state.covered) { - throw new Error( - 'Agent ' + gAgent.id + ' is hidden behind group' - ); + throw new Error('Agent ' + name + ' is hidden behind group'); } if(!allowGrouped && state.group !== null) { - throw new Error('Agent ' + gAgent.id + ' is in a group'); + throw new Error('Agent ' + name + ' is in a group'); } if(!allowVirtual && gAgent.isVirtualSource) { - throw new Error('cannot use message source here'); + throw new Error('Cannot use message source here'); } - if(gAgent.id.startsWith('__')) { - throw new Error(gAgent.id + ' is a reserved name'); + if(isReservedAgentName(name)) { + throw new Error(name + ' is a reserved name'); } }); } @@ -1861,9 +1873,9 @@ this.defineGAgents(filteredGAgents); return { - type: (visible ? 'agent begin' : 'agent end'), agentIDs: filteredGAgents.map((gAgent) => gAgent.id), mode, + type: (visible ? 'agent begin' : 'agent end'), }; } @@ -1887,16 +1899,16 @@ }); return { - type: 'agent highlight', agentIDs: filteredGAgents.map((gAgent) => gAgent.id), highlighted, + type: 'agent highlight', }; } _makeSection(header, stages) { return { - header, delayedConnections: new Map(), + header, stages, }; } @@ -1918,21 +1930,21 @@ const gAgents = [leftGAgent, rightGAgent]; const stages = []; this.currentSection = this._makeSection({ - type: 'block begin', blockType, - tag: this.textFormatter(tag), - label: this.textFormatter(label), canHide: true, + label: this.textFormatter(label), left: leftGAgent.id, - right: rightGAgent.id, ln, + right: rightGAgent.id, + tag: this.textFormatter(tag), + type: 'block begin', }, stages); this.currentNest = { blockType, gAgents, + hasContent: false, leftGAgent, rightGAgent, - hasContent: false, sections: [this.currentSection], }; this.replaceGAgentState(leftGAgent, AgentState.LOCKED); @@ -1956,10 +1968,10 @@ handleBlockBegin({ln, blockType, tag, label}) { this.beginNested(blockType, { - tag, label, - name: this.nextBlockName(), ln, + name: this.nextBlockName(), + tag, }); } @@ -1972,13 +1984,13 @@ } this._checkSectionEnd(); this.currentSection = this._makeSection({ - type: 'block split', blockType, - tag: this.textFormatter(tag), label: this.textFormatter(label), left: this.currentNest.leftGAgent.id, - right: this.currentNest.rightGAgent.id, ln, + right: this.currentNest.rightGAgent.id, + tag: this.textFormatter(tag), + type: 'block split', }, []); this.currentNest.sections.push(this.currentSection); } @@ -2005,9 +2017,9 @@ this.currentSection.stages.push(...section.stages); }); this.addStage({ - type: 'block end', left: nested.leftGAgent.id, right: nested.rightGAgent.id, + type: 'block end', }); } else { throw new Error('Empty block'); @@ -2046,10 +2058,10 @@ return { gAgents, - leftGAgent, - rightGAgent, gAgentsContained, gAgentsCovered, + leftGAgent, + rightGAgent, }; } @@ -2065,13 +2077,13 @@ this.activeGroups.set(alias, details); this.addStage(this.setGAgentVis(details.gAgents, true, 'box')); this.addStage({ - type: 'block begin', blockType, - tag: this.textFormatter(tag), canHide: false, label: this.textFormatter(label), left: details.leftGAgent.id, right: details.rightGAgent.id, + tag: this.textFormatter(tag), + type: 'block begin', }); } @@ -2091,23 +2103,23 @@ this.updateGAgentState(GAgent.make(name), {group: null}); return { - type: 'block end', left: details.leftGAgent.id, right: details.rightGAgent.id, + type: 'block end', }; } handleMark({name}) { this.markers.add(name); - this.addStage({type: 'mark', name}, false); + this.addStage({name, type: 'mark'}, false); } handleDivider({mode, height, label}) { this.addStage({ - type: 'divider', - mode, - height, formattedLabel: this.textFormatter(label), + height, + mode, + type: 'divider', }, false); } @@ -2115,7 +2127,7 @@ if(target !== '' && !this.markers.has(target)) { throw new Error('Unknown marker: ' + target); } - this.addStage({type: 'async', target}, false); + this.addStage({target, type: 'async'}, false); } handleLabelPattern({pattern}) { @@ -2318,15 +2330,15 @@ const tag = {}; this.handleConnectDelayBegin({ agent: agents[0], - tag, - options, ln: 0, + options, + tag, }); this.handleConnectDelayEnd({ agent: agents[1], - tag, label, options, + tag, }); return; } @@ -2337,10 +2349,10 @@ gAgents = this.expandVirtualSourceAgents(gAgents); const connectStage = { - type: 'connect', agentIDs: gAgents.map((gAgent) => gAgent.id), label: this.textFormatter(this.applyLabelPattern(label)), options, + type: 'connect', }; this.addParallelStages(this._makeConnectParallelStages( @@ -2359,14 +2371,14 @@ const uniqueTag = this.nextVirtualAgentName(); const connectStage = { - type: 'connect-delay-begin', - tag: uniqueTag, agentIDs: null, label: null, options, + tag: uniqueTag, + type: 'connect-delay-begin', }; - dcs.set(tag, {tag, uniqueTag, ln, gAgents, connectStage}); + dcs.set(tag, {connectStage, gAgents, ln, tag, uniqueTag}); this.addParallelStages(this._makeConnectParallelStages( flags, @@ -2406,8 +2418,8 @@ }); const connectEndStage = { - type: 'connect-delay-end', tag: dcInfo.uniqueTag, + type: 'connect-delay-end', }; this.addParallelStages(this._makeConnectParallelStages( @@ -2438,18 +2450,18 @@ this.defineGAgents(gAgents); this.addStage({ - type, agentIDs, - mode, label: this.textFormatter(label), + mode, + type, }); } handleAgentDefine({agents}) { const gAgents = agents.map(this.toGAgent); this.validateGAgents(gAgents, { - allowGrouped: true, allowCovered: true, + allowGrouped: true, }); mergeSets(this.gAgents, gAgents, GAgent.equals); } @@ -2458,8 +2470,8 @@ const gAgent = this.toGAgent(agent); const gAgents = [gAgent]; this.validateGAgents(gAgents, { - allowGrouped: true, allowCovered: true, + allowGrouped: true, }); mergeSets(this.gAgents, gAgents, GAgent.equals); @@ -2537,10 +2549,10 @@ this.textFormatter = meta.textFormatter; const globals = this.beginNested('global', { - tag: '', label: '', - name: '', ln: 0, + name: '', + tag: '', }); stages.forEach(this.handleStage); @@ -2568,12 +2580,12 @@ swapFirstBegin(globals.stages, meta.headers || 'box'); return { - meta: { - title: this.textFormatter(meta.title), - theme: meta.theme, - code: meta.code, - }, agents: this.gAgents.slice(), + meta: { + code: meta.code, + theme: meta.theme, + title: this.textFormatter(meta.title), + }, stages: globals.stages, }; } @@ -2981,11 +2993,18 @@ } } - /* eslint-disable max-lines */ + /* + * The order of commands inside "then" blocks directly influences the + * order they are displayed to the user in autocomplete menus. + * This relies on the fact that common JS engines maintain insertion + * order in objects, though this is not guaranteed. It could be switched + * to use Map objects instead for strict compliance, at the cost of + * extra syntax. + */ - const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}}; + const CM_ERROR = {type: 'error line-error', suggest: [], then: {'': 0}}; - function textTo(exit, suggest = false) { + function textTo(exit, suggest = []) { return { type: 'string', suggest, @@ -3008,19 +3027,10 @@ ]; const makeCommands = ((() => { - /* - * The order of commands inside "then" blocks directly influences the - * order they are displayed to the user in autocomplete menus. - * This relies on the fact that common JS engines maintain insertion - * order in objects, though this is not guaranteed. It could be switched - * to use Map objects instead for strict compliance, at the cost of - * extra syntax. - */ - function agentListTo(exit, next = 1) { return { type: 'variable', - suggest: {known: 'Agent'}, + suggest: [{known: 'Agent'}], then: Object.assign({}, exit, { '': 0, ',': {type: 'operator', then: {'': next}}, @@ -3028,8 +3038,8 @@ }; } - const end = {type: '', suggest: '\n', then: {}}; - const hiddenEnd = {type: '', suggest: false, then: {}}; + const end = {type: '', suggest: ['\n'], then: {}}; + const hiddenEnd = {type: '', suggest: [], then: {}}; const textToEnd = textTo({'\n': end}); const colonTextToEnd = { type: 'operator', @@ -3038,7 +3048,7 @@ const aliasListToEnd = agentListTo({ '\n': end, 'as': {type: 'keyword', then: { - '': {type: 'variable', suggest: {known: 'Agent'}, then: { + '': {type: 'variable', suggest: [{known: 'Agent'}], then: { '': 0, ',': {type: 'operator', then: {'': 3}}, '\n': end, @@ -3049,7 +3059,7 @@ const agentToOptText = { type: 'variable', - suggest: {known: 'Agent'}, + suggest: [{known: 'Agent'}], then: { '': 0, ':': {type: 'operator', then: { @@ -3065,7 +3075,7 @@ 'as': {type: 'keyword', then: { '': { type: 'variable', - suggest: {known: 'Agent'}, + suggest: [{known: 'Agent'}], then: { '': 0, '\n': end, @@ -3196,7 +3206,7 @@ '...': {type: 'operator', then: { '': { type: 'variable', - suggest: {known: 'DelayedAgent'}, + suggest: [{known: 'DelayedAgent'}], then: { '': 0, ':': CM_ERROR, @@ -3218,14 +3228,14 @@ const hiddenLabelIndicator = { type: 'operator', - suggest: false, + suggest: [], override: 'Label', then: {}, }; const firstAgent = { type: 'variable', - suggest: {known: 'Agent'}, + suggest: [{known: 'Agent'}], then: Object.assign({ '': 0, }, connectors, { @@ -3235,7 +3245,7 @@ const firstAgentDelayed = { type: 'variable', - suggest: {known: 'DelayedAgent'}, + suggest: [{known: 'DelayedAgent'}], then: Object.assign({ '': 0, ':': hiddenLabelIndicator, @@ -3277,10 +3287,7 @@ 'theme': {type: 'keyword', then: { '': { type: 'string', - suggest: { - global: 'themes', - suffix: '\n', - }, + suggest: [{global: 'themes', suffix: '\n'}], then: { '': 0, '\n': end, @@ -3323,7 +3330,7 @@ }}, 'if': commonGroup, 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { - 'if': {type: 'keyword', suggest: 'if: ', then: { + 'if': {type: 'keyword', suggest: ['if: '], then: { '': textToEnd, ':': {type: 'operator', then: { '': textToEnd, @@ -3343,11 +3350,11 @@ '': agentListTo({':': CM_ERROR}, agentListToText), }}, }}, - 'state': {type: 'keyword', suggest: 'state over ', then: { + 'state': {type: 'keyword', suggest: ['state over '], then: { 'over': {type: 'keyword', then: { '': { type: 'variable', - suggest: {known: 'Agent'}, + suggest: [{known: 'Agent'}], then: { '': 0, ',': CM_ERROR, @@ -3371,7 +3378,7 @@ 'simultaneously': {type: 'keyword', then: { ':': {type: 'operator', then: {}}, 'with': {type: 'keyword', then: { - '': {type: 'variable', suggest: {known: 'Label'}, then: { + '': {type: 'variable', suggest: [{known: 'Label'}], then: { '': 0, ':': {type: 'operator', then: {}}, }}, @@ -3385,33 +3392,32 @@ }); })()); + /* eslint-enable sort-keys */ + function cmCappedToken(token, current) { if(Object.keys(current.then).length > 0) { - return {v: token, suffix: ' ', q: false}; + return {q: false, suffix: ' ', v: token}; } else { - return {v: token, suffix: '\n', q: false}; + return {q: false, suffix: '\n', v: token}; } } function cmGetSuggestions(state, token, current) { - let suggestions = current.suggest; - if(!Array.isArray(suggestions)) { - suggestions = [suggestions]; - } + const suggestions = current.suggest || ['']; return flatMap(suggestions, (suggest) => { - if(suggest === false) { - return []; - } else if(typeof suggest === 'object') { + if(typeof suggest === 'object') { if(suggest.known) { return state['known' + suggest.known] || []; } else { return [suggest]; } - } else if(typeof suggest === 'string' && suggest) { - return [{v: suggest, q: (token === '')}]; - } else { + } else if(suggest === '') { return [cmCappedToken(token, current)]; + } else if(typeof suggest === 'string') { + return [{q: (token === ''), v: suggest}]; + } else { + throw new Error('Invalid suggestion type ' + suggest); } }); } @@ -3433,30 +3439,40 @@ return comp; } - function updateSuggestion(state, locals, token, {suggest, override}) { - let known = null; - if(typeof suggest === 'object' && suggest.known) { - known = suggest.known; - } - if(locals.type && known !== locals.type) { - if(override) { - locals.type = override; + function getSuggestionCategory(suggestions) { + for(const suggestion of suggestions) { + if(typeof suggestion === 'object' && suggestion.known) { + return suggestion.known; } - mergeSets( - state['known' + locals.type], - [{v: locals.value, suffix: ' ', q: true}], - suggestionsEqual - ); - locals.type = ''; + } + return null; + } + + function appendToken(base, token) { + return base + (base ? token.s : '') + token.v; + } + + function storeKnownEntity(state, type, value) { + mergeSets( + state['known' + type], + [{q: true, suffix: ' ', v: value}], + suggestionsEqual + ); + } + + function updateKnownEntities(state, locals, token, current) { + const known = getSuggestionCategory(current.suggest || ['']); + + if(locals.type && known !== locals.type) { + storeKnownEntity(state, current.override || locals.type, locals.value); locals.value = ''; } + if(known) { - locals.type = known; - if(locals.value) { - locals.value += token.s; - } - locals.value += token.v; + locals.value = appendToken(locals.value, token); } + + locals.type = known; } function cmCheckToken(state, eol, commands) { @@ -3485,10 +3501,10 @@ path.push(found || CM_ERROR); } current = last(path); - updateSuggestion(state, suggestions, token, current); + updateKnownEntities(state, suggestions, token, current); }); if(eol) { - updateSuggestion(state, suggestions, null, {}); + updateKnownEntities(state, suggestions, null, {}); } state.nextCompletions = cmMakeCompletions(state, path); state.valid = ( @@ -3501,11 +3517,13 @@ function getInitialToken(block) { const baseToken = (block.baseToken || {}); return { - value: baseToken.v || '', quoted: baseToken.q || false, + value: baseToken.v || '', }; } + const NO_TOKEN = -1; + class Mode { constructor(tokenDefinitions, arrows) { this.tokenDefinitions = tokenDefinitions; @@ -3515,20 +3533,20 @@ startState() { return { - currentType: -1, + beginCompletions: cmMakeCompletions({}, [this.commands]), + completions: [], current: '', - currentSpace: '', currentQuoted: false, + currentSpace: '', + currentType: NO_TOKEN, + indent: 0, + isVar: true, knownAgent: [], knownDelayedAgent: [], knownLabel: [], - beginCompletions: cmMakeCompletions({}, [this.commands]), - completions: [], + line: [], nextCompletions: [], valid: true, - isVar: true, - line: [], - indent: 0, }; } @@ -3567,14 +3585,14 @@ _addToken(state) { state.line.push({ - v: state.current, - s: state.currentSpace, q: state.currentQuoted, + s: state.currentSpace, + v: state.current, }); } _tokenEndFound(stream, state, block, match) { - state.currentType = -1; + state.currentType = NO_TOKEN; if(block.includeEnd) { state.current += match[0]; } @@ -3615,21 +3633,33 @@ } } + _tokenContinueOrBegin(stream, state) { + if(state.currentType === NO_TOKEN) { + if(stream.sol()) { + state.line.length = 0; + } + if(!this._tokenBegin(stream, state)) { + return ''; + } + } + return this._tokenEnd(stream, state); + } + + _isLineTerminated(state) { + return state.currentType !== NO_TOKEN || state.valid; + } + token(stream, state) { state.completions = state.nextCompletions; state.isVar = true; - if(stream.sol() && state.currentType === -1) { - state.line.length = 0; - } - let type = ''; - if(state.currentType !== -1 || this._tokenBegin(stream, state)) { - type = this._tokenEnd(stream, state); - } - if(state.currentType === -1 && stream.eol() && !state.valid) { + + const type = this._tokenContinueOrBegin(stream, state); + + if(stream.eol() && !this._isLineTerminated(state)) { return 'line-error ' + type; - } else { - return type; } + + return type; } indent(state) { @@ -4031,7 +4061,7 @@ return result; } - /* eslint-disable max-lines */ + /* eslint-disable sort-keys */ // Maybe later const BLOCK_TYPES = { 'if': { @@ -4115,17 +4145,47 @@ 'text': { mode: 'text', types: { - 'left': {type: 'note left', skip: ['of'], min: 0, max: null}, - 'right': {type: 'note right', skip: ['of'], min: 0, max: null}, + 'left': { + type: 'note left', + skip: ['of'], + min: 0, + max: Number.POSITIVE_INFINITY, + }, + 'right': { + type: 'note right', + skip: ['of'], + min: 0, + max: Number.POSITIVE_INFINITY, + }, }, }, 'note': { mode: 'note', types: { - 'over': {type: 'note over', skip: [], min: 0, max: null}, - 'left': {type: 'note left', skip: ['of'], min: 0, max: null}, - 'right': {type: 'note right', skip: ['of'], min: 0, max: null}, - 'between': {type: 'note between', skip: [], min: 2, max: null}, + 'over': { + type: 'note over', + skip: [], + min: 0, + max: Number.POSITIVE_INFINITY, + }, + 'left': { + type: 'note left', + skip: ['of'], + min: 0, + max: Number.POSITIVE_INFINITY, + }, + 'right': { + type: 'note right', + skip: ['of'], + min: 0, + max: Number.POSITIVE_INFINITY, + }, + 'between': { + type: 'note between', + skip: [], + min: 2, + max: Number.POSITIVE_INFINITY, + }, }, }, 'state': { @@ -4160,15 +4220,20 @@ return new Error(message + suffix); } - function joinLabel(line, begin = 0, end = null) { + function endIndexInLine(line, end = null) { if(end === null) { - end = line.length; + return line.length; } - if(end <= begin) { + return end; + } + + function joinLabel(line, begin = 0, end = null) { + const e = endIndexInLine(line, end); + if(e <= begin) { return ''; } let result = line[begin].v; - for(let i = begin + 1; i < end; ++ i) { + for(let i = begin + 1; i < e; ++ i) { result += line[i].s + line[i].v; } return result; @@ -4204,30 +4269,23 @@ return start + skip.length; } - function findToken(line, tokens, { + function findTokens(line, tokens, { start = 0, limit = null, orEnd = false, } = {}) { - if(limit === null) { - limit = line.length; - } - if(!Array.isArray(tokens)) { - tokens = [tokens]; - } - for(let i = start; i <= limit - tokens.length; ++ i) { + const e = endIndexInLine(line, limit); + for(let i = start; i <= e - tokens.length; ++ i) { if(skipOver(line, i, tokens) !== i) { return i; } } - return orEnd ? limit : -1; + return orEnd ? e : -1; } function findFirstToken(line, tokenMap, {start = 0, limit = null} = {}) { - if(limit === null) { - limit = line.length; - } - for(let pos = start; pos < limit; ++ pos) { + const e = endIndexInLine(line, limit); + for(let pos = start; pos < e; ++ pos) { const value = tokenMap.get(tokenKeyword(line[pos])); if(value) { return {pos, value}; @@ -4237,12 +4295,9 @@ } function readAgentAlias(line, start, end, {enableAlias, allowBlankName}) { - let aliasSep = -1; + let aliasSep = end; if(enableAlias) { - aliasSep = findToken(line, 'as', {start}); - } - if(aliasSep === -1 || aliasSep >= end) { - aliasSep = end; + aliasSep = findTokens(line, ['as'], {limit: end, orEnd: true, start}); } if(start >= aliasSep && !allowBlankName) { let errPosToken = line[start]; @@ -4311,29 +4366,17 @@ } const PARSERS = [ - (line, meta) => { // Title - if(tokenKeyword(line[0]) !== 'title') { - return null; - } - + {begin: ['title'], fn: (line, meta) => { // Title meta.title = joinLabel(line, 1); return true; - }, - - (line, meta) => { // Theme - if(tokenKeyword(line[0]) !== 'theme') { - return null; - } + }}, + {begin: ['theme'], fn: (line, meta) => { // Theme meta.theme = joinLabel(line, 1); return true; - }, - - (line, meta) => { // Terminators - if(tokenKeyword(line[0]) !== 'terminators') { - return null; - } + }}, + {begin: ['terminators'], fn: (line, meta) => { // Terminators const type = tokenKeyword(line[1]); if(!type) { throw makeError('Unspecified termination', line[0]); @@ -4343,13 +4386,9 @@ } meta.terminators = type; return true; - }, - - (line, meta) => { // Headers - if(tokenKeyword(line[0]) !== 'headers') { - return null; - } + }}, + {begin: ['headers'], fn: (line, meta) => { // Headers const type = tokenKeyword(line[1]); if(!type) { throw makeError('Unspecified header', line[0]); @@ -4359,15 +4398,11 @@ } meta.headers = type; return true; - }, + }}, - (line) => { // Divider - if(tokenKeyword(line[0]) !== 'divider') { - return null; - } - - const labelSep = findToken(line, ':', {orEnd: true}); - const heightSep = findToken(line, ['with', 'height'], { + {begin: ['divider'], fn: (line) => { // Divider + const labelSep = findTokens(line, [':'], {orEnd: true}); + const heightSep = findTokens(line, ['with', 'height'], { limit: labelSep, orEnd: true, }); @@ -4393,13 +4428,9 @@ height, label: joinLabel(line, labelSep + 1), }; - }, - - (line) => { // Autolabel - if(tokenKeyword(line[0]) !== 'autolabel') { - return null; - } + }}, + {begin: ['autolabel'], fn: (line) => { // Autolabel let raw = null; if(tokenKeyword(line[1]) === 'off') { raw = '