Skip to content
This repository was archived by the owner on Dec 17, 2024. It is now read-only.

Commit b709d71

Browse files
committed
improved error handling when composing in-shell
Fixes #805
1 parent 13516bb commit b709d71

File tree

6 files changed

+123
-17
lines changed

6 files changed

+123
-17
lines changed

app/plugins/modules/composer/lib/create-from-source.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
const debug = require('debug')('composer compilation')
18+
1719
const vm = require('vm'),
1820
fs = require('fs'),
1921
path = require('path'),
@@ -94,6 +96,7 @@ exports.compileToFSM = (src, opts={}) => new Promise((resolve, reject) => {
9496
logMessage = ''
9597
try {
9698
const doExit = () => reject({
99+
statusCode: 'ENOPARSE',
97100
fsm: errorMessage,
98101
code: originalCode
99102
})
@@ -169,25 +172,32 @@ ${errorMessage}`)
169172

170173
// for parse error, error message is shown in the fsm (JSON) tab, and user code in the source (code) tab
171174
// reject now returns {fsm:errMsg, code:originalCode}
172-
reject(
173-
{
174-
fsm: message,
175-
code: originalCode
176-
}
177-
)
175+
return {
176+
statusCode: 'ENOPARSE', // would like to use code here, but we've already used it for code:originalCode
177+
message,
178+
fsm: message,
179+
code: originalCode
180+
}
178181
}
179182
}
180183
let fsm
181184
try {
182185
fsm = compile(originalCode)
183186
} catch (err) {
187+
console.error('Catastrophic internal error compiling source')
184188
console.error(err)
185189
}
186190

187191
if (!isValidFSM(fsm)) {
188192
// still no luck? reject
189193
console.error('Error compiling app source', fsm, sandbox)
190-
reject('Your code could not be composed')
194+
if (fsm.statusCode) {
195+
debug('rejecting with pre-made error')
196+
reject(fsm)
197+
} else {
198+
reject('Your code could not be composed')
199+
}
200+
191201
} else {
192202
if (opts.code) {
193203
resolve({fsm, code: originalCode, localCodePath})

app/plugins/modules/composer/lib/create.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ debug('finished loading modules')
3333
*/
3434
const handleFailure_fsmPromise = err => {
3535
if (err.fsm) {
36-
throw new Error(err.fsm)
36+
const error = new Error(err.fsm)
37+
for (let key in err) {
38+
error[key] = err[key]
39+
}
40+
throw error
3741
} else {
3842
throw err
3943
}

app/plugins/modules/editor/lib/edit.js

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const persisters = {
8787
// persisters for regular actions
8888
actions: {
8989
getCode: action => action,
90-
save: (wsk, action) => {
90+
save: (wsk, action, editor) => {
9191
const owOpts = wsk.owOpts({
9292
name: action.name,
9393
namespace: action.namespace,
@@ -130,11 +130,11 @@ const persisters = {
130130
}
131131
}
132132
}),
133-
save: (wsk, app) => new Promise((resolve, reject) => {
133+
save: (wsk, app, editor) => new Promise((resolve, reject) => {
134134
const fs = require('fs'),
135135
tmp = require('tmp')
136136

137-
tmp.file({ prefix: 'shell-', postfix: '.js' }, (err, path, fd, cleanup) => {
137+
tmp.file({ prefix: 'shell-', postfix: '.js' }, (err, filepath, fd, cleanup) => {
138138
if (err) {
139139
reject(err)
140140
} else {
@@ -143,12 +143,65 @@ const persisters = {
143143
reject(err)
144144
} else {
145145
// -r means try to deploy the actions, too
146-
return repl.qexec(`app update "${app.name}" "${path}" -r`)
146+
return repl.qexec(`app update "${app.name}" "${filepath}" -r`)
147147
.then(app => {
148148
cleanup()
149-
console.error('#####', app)
150149
resolve(app)
151150
})
151+
.then(res => {
152+
// successful compilation, so remove any parse error decorations
153+
editor.clearDecorations()
154+
return res
155+
})
156+
.catch(err => {
157+
console.error('!!!!!!!', err)
158+
if (err.statusCode === 'ENOPARSE') {
159+
debug('composition did not parse', err)
160+
const basename = path.basename(filepath),
161+
pattern = new RegExp('\\n([^\n]+)\\n\\s+at\\s+' + basename.replace(/\./, '\\.') + ':(\\d+):(\\d+)'),
162+
match = err.message.match(pattern)
163+
164+
debug('pattern', pattern)
165+
debug('message', err.message)
166+
if (match) {
167+
const problem = match[1],
168+
line = match[2],
169+
column = match[3]
170+
debug('got match', problem, line, column)
171+
172+
// see the 'hack it ourselves' just below
173+
const rando = `shell-${new Date().getTime()}`
174+
175+
editor.__currentDecorations = editor.deltaDecorations(editor.__currentDecorations || [], [
176+
{ range: new monaco.Range(line,1,line,1),
177+
options: { isWholeLine: true,
178+
//glyphMarginClassName: 'editor__parse-error-gutter-marker editor__parse_error_decoration',
179+
//glyphMarginHoverMessage: problem
180+
linesDecorationsClassName: `editor__parse-error-gutter-marker editor__parse-error-decoration ${rando}`
181+
}
182+
},
183+
{ range: new monaco.Range(line,column,line,column + 1),
184+
options: {
185+
beforeContentClassName: `editor__parse-error-before-marker editor__parse-error-decoration ${rando}`,
186+
//inlineClassName: 'editor__parse-error-inline-marker',
187+
hoverMessage: problem
188+
}
189+
},
190+
])
191+
192+
// glyphMarginHoverMessage seems broken; hack it ourselves for now
193+
setTimeout(() => {
194+
const decos = document.querySelectorAll(`.${rando}`)
195+
if (decos) {
196+
for (let idx = 0; idx < decos.length; idx++) {
197+
const deco = decos[idx]
198+
deco.setAttribute('title', problem)
199+
}
200+
}
201+
}, 0)
202+
}
203+
}
204+
})
152205
}
153206
})
154207
}
@@ -177,7 +230,7 @@ const save = ({wsk, getAction, editor, eventBus}) => ({
177230
// https://github.com/apache/incubator-openwhisk/issues/3237
178231
delete action.version
179232

180-
return save(wsk, action)
233+
return save(wsk, action, editor)
181234
.then(action => {
182235
action.persister = persister
183236
eventBus.emit('/editor/save', action, { event: 'save' })
@@ -385,6 +438,7 @@ const openEditor = wsk => {
385438
minimap: {
386439
enabled: false
387440
},
441+
//glyphMargin: true, // needed for error indicators
388442
autoIndent: true,
389443
codeLens: false,
390444
quickSuggestions: false,
@@ -400,6 +454,11 @@ const openEditor = wsk => {
400454
language: 'javascript'
401455
})
402456

457+
editor.clearDecorations = () => {
458+
debug('clearing decorations')
459+
editor.__currentDecorations = editor.deltaDecorations(editor.__currentDecorations || [], [])
460+
}
461+
403462
resolve(editor)
404463
})
405464
}
@@ -455,8 +514,11 @@ const openEditor = wsk => {
455514
modified.className = 'is-modified'
456515

457516
// even handlers for saved and content-changed
458-
const editsInProgress = () => sidecar.classList.add('is-modified') // edits in progress
459-
const editsCommitted = action => { // edits committed
517+
const editsInProgress = () => {
518+
sidecar.classList.add('is-modified')
519+
editor.clearDecorations() // for now, don't trty to be clever; remove decorations on any edit
520+
}
521+
const editsCommitted = action => {
460522
const lockIcon = sidecar.querySelector('[data-mode="lock"]')
461523

462524
sidecar.classList.remove('is-modified')

app/plugins/modules/editor/lib/editor.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,20 @@ body.sidecar-full-screen #sidecar .wskflow-container.visible {
101101
max-height: none;
102102
max-width: 50%;
103103
}
104+
105+
106+
/* gutter and inline decorations */
107+
.editor.parse-error-gutter-marker {
108+
border-radius: 0.9375em;
109+
background-color: var(--color-support-01);
110+
width: 1ex !important;
111+
margin-left: 1em;
112+
/*background-image: repeating-linear-gradient(-45deg, transparent, transparent 10%, rgba(255,255,255,.5) 10%, rgba(255,255,255,.5) 15%);*/
113+
}
114+
.editor.parse-error-before-marker:before {
115+
content: '^';
116+
font-weight: bold;
117+
position: absolute;
118+
bottom: -1em;
119+
color: var(--color-support-01);
120+
}

tests/tests/passes/07/composer-create-error-handling.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ SyntaxError: Unexpected token ,` }]
9595
.catch(common.oops(this)))
9696
dryRunBad.forEach( ({input, err}) => {
9797
it(`should dry-run check with expected error ${input} --dry-run`, () => cli.do(`app create ${input} --dry-run`, this.app)
98-
.then(cli.expectError(0, err))
98+
.then(cli.expectError('ENOPARSE', err))
9999
.catch(common.oops(this)))
100100
})
101101

tests/tests/passes/07/composer-edit.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,17 @@ describe('edit compositions', function() {
8181
.then(deploy(this.app, 'comp2'))
8282
.then(sidecar.expectBadge('v0.0.2'))
8383
.catch(common.oops(this)))
84+
85+
it(`should fail to open the editor for compose against existing composition`, () => cli.do('compose comp2', this.app)
86+
.then(cli.expectError(409))
87+
.catch(common.oops(this)))
88+
89+
it(`should open the editor to a new composition and expect error handling`, () => cli.do('compose comp3', this.app)
90+
.then(cli.expectOK)
91+
.then(sidecar.expectOpen)
92+
.then(sidecar.expectShowing('comp3'))
93+
.then(() => this.app.client.keys('composer.sequence(notfound1, notfound2)'))
94+
.then(() => this.app.client.click(ui.selectors.SIDECAR_MODE_BUTTON('Deploy')))
95+
.then(() => this.app.client.waitForExist('.editor.parse-error-decoration'))
96+
.catch(common.oops(this)))
8497
})

0 commit comments

Comments
 (0)