| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 28870a4 commit e4f3468
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,79 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const acorn = require('internal/deps/acorn/dist/acorn'); | ||
| 4 | + const { tokTypes: tt } = acorn; | ||
| 5 | + | ||
| 6 | + // If the error is that we've unexpectedly ended the input, | ||
| 7 | + // then let the user try to recover by adding more input. | ||
| 8 | + // Note: `e` (the original exception) is not used by the current implemention, | ||
| 9 | + // but may be needed in the future. | ||
| 10 | + function isRecoverableError(e, code) { | ||
| 11 | + let recoverable = false; | ||
| 12 | + | ||
| 13 | + // Determine if the point of the any error raised is at the end of the input. | ||
| 14 | + // There are two cases to consider: | ||
| 15 | + // | ||
| 16 | + // 1. Any error raised after we have encountered the 'eof' token. | ||
| 17 | + // This prevents us from declaring partial tokens (like '2e') as | ||
| 18 | + // recoverable. | ||
| 19 | + // | ||
| 20 | + // 2. Three cases where tokens can legally span lines. This is | ||
| 21 | + // template, comment, and strings with a backslash at the end of | ||
| 22 | + // the line, indicating a continuation. Note that we need to look | ||
| 23 | + // for the specific errors of 'unterminated' kind (not, for example, | ||
| 24 | + // a syntax error in a ${} expression in a template), and the only | ||
| 25 | + // way to do that currently is to look at the message. Should Acorn | ||
| 26 | + // change these messages in the future, this will lead to a test | ||
| 27 | + // failure, indicating that this code needs to be updated. | ||
| 28 | + // | ||
| 29 | + acorn.plugins.replRecoverable = (parser) => { | ||
| 30 | + parser.extend('nextToken', (nextToken) => { | ||
| 31 | + return function() { | ||
| 32 | + Reflect.apply(nextToken, this, []); | ||
| 33 | + | ||
| 34 | + if (this.type === tt.eof) recoverable = true; | ||
| 35 | + }; | ||
| 36 | + }); | ||
| 37 | + | ||
| 38 | + parser.extend('raise', (raise) => { | ||
| 39 | + return function(pos, message) { | ||
| 40 | + switch (message) { | ||
| 41 | + case 'Unterminated template': | ||
| 42 | + case 'Unterminated comment': | ||
| 43 | + recoverable = true; | ||
| 44 | + break; | ||
| 45 | + | ||
| 46 | + case 'Unterminated string constant': | ||
| 47 | + const token = this.input.slice(this.lastTokStart, this.pos); | ||
| 48 | + // see https://www.ecma-international.org/ecma-262/#sec-line-terminators | ||
| 49 | + recoverable = /\\(?:\r\n?|\n|\u2028|\u2029)$/.test(token); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + Reflect.apply(raise, this, [pos, message]); | ||
| 53 | + }; | ||
| 54 | + }); | ||
| 55 | + }; | ||
| 56 | + | ||
| 57 | + // For similar reasons as `defaultEval`, wrap expressions starting with a | ||
| 58 | + // curly brace with parenthesis. Note: only the open parenthesis is added | ||
| 59 | + // here as the point is to test for potentially valid but incomplete | ||
| 60 | + // expressions. | ||
| 61 | + if (/^\s*\{/.test(code) && isRecoverableError(e, `(${code}`)) return true; | ||
| 62 | + | ||
| 63 | + // Try to parse the code with acorn. If the parse fails, ignore the acorn | ||
| 64 | + // error and return the recoverable status. | ||
| 65 | + try { | ||
| 66 | + acorn.parse(code, { plugins: { replRecoverable: true } }); | ||
| 67 | + | ||
| 68 | + // Odd case: the underlying JS engine (V8, Chakra) rejected this input | ||
| 69 | + // but Acorn detected no issue. Presume that additional text won't | ||
| 70 | + // address this issue. | ||
| 71 | + return false; | ||
| 72 | + } catch { | ||
| 73 | + return recoverable; | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + module.exports = { | ||
| 78 | + isRecoverableError | ||
| 79 | + }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -73,6 +73,7 @@ const { | |||
| 73 | 73 | } = require('internal/errors').codes; | |
| 74 | 74 | const { sendInspectorCommand } = require('internal/util/inspector'); | |
| 75 | 75 | const { experimentalREPLAwait } = process.binding('config'); | |
| 76 | + const { isRecoverableError } = require('internal/repl/recoverable'); | ||
| 76 | 77 | ||
| 77 | 78 | // Lazy-loaded. | |
| 78 | 79 | let processTopLevelAwait; | |
@@ -227,7 +228,8 @@ function REPLServer(prompt, | |||
| 227 | 228 | // It's confusing for `{ a : 1 }` to be interpreted as a block | |
| 228 | 229 | // statement rather than an object literal. So, we first try | |
| 229 | 230 | // to wrap it in parentheses, so that it will be interpreted as | |
| 230 | - // an expression. | ||
| 231 | + // an expression. Note that if the above condition changes, | ||
| 232 | + // lib/internal/repl/recoverable.js needs to be changed to match. | ||
| 231 | 233 | code = `(${code.trim()})\n`; | |
| 232 | 234 | wrappedCmd = true; | |
| 233 | 235 | } | |
@@ -1505,76 +1507,6 @@ function regexpEscape(s) { | |||
| 1505 | 1507 | return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); | |
| 1506 | 1508 | } | |
| 1507 | 1509 | ||
| 1508 | - // If the error is that we've unexpectedly ended the input, | ||
| 1509 | - // then let the user try to recover by adding more input. | ||
| 1510 | - function isRecoverableError(e, code) { | ||
| 1511 | - if (e && e.name === 'SyntaxError') { | ||
| 1512 | - var message = e.message; | ||
| 1513 | - if (message === 'Unterminated template literal' || | ||
| 1514 | - message === 'Unexpected end of input') { | ||
| 1515 | - return true; | ||
| 1516 | - } | ||
| 1517 | - | ||
| 1518 | - if (message === 'missing ) after argument list') { | ||
| 1519 | - const frames = e.stack.split(/\r?\n/); | ||
| 1520 | - const pos = frames.findIndex((f) => f.match(/^\s*\^+$/)); | ||
| 1521 | - return pos > 0 && frames[pos - 1].length === frames[pos].length; | ||
| 1522 | - } | ||
| 1523 | - | ||
| 1524 | - if (message === 'Invalid or unexpected token') | ||
| 1525 | - return isCodeRecoverable(code); | ||
| 1526 | - } | ||
| 1527 | - return false; | ||
| 1528 | - } | ||
| 1529 | - | ||
| 1530 | - // Check whether a code snippet should be forced to fail in the REPL. | ||
| 1531 | - function isCodeRecoverable(code) { | ||
| 1532 | - var current, previous, stringLiteral; | ||
| 1533 | - var isBlockComment = false; | ||
| 1534 | - var isSingleComment = false; | ||
| 1535 | - var isRegExpLiteral = false; | ||
| 1536 | - var lastChar = code.charAt(code.length - 2); | ||
| 1537 | - var prevTokenChar = null; | ||
| 1538 | - | ||
| 1539 | - for (var i = 0; i < code.length; i++) { | ||
| 1540 | - previous = current; | ||
| 1541 | - current = code[i]; | ||
| 1542 | - | ||
| 1543 | - if (previous === '\\' && (stringLiteral || isRegExpLiteral)) { | ||
| 1544 | - current = null; | ||
| 1545 | - } else if (stringLiteral) { | ||
| 1546 | - if (stringLiteral === current) { | ||
| 1547 | - stringLiteral = null; | ||
| 1548 | - } | ||
| 1549 | - } else if (isRegExpLiteral && current === '/') { | ||
| 1550 | - isRegExpLiteral = false; | ||
| 1551 | - } else if (isBlockComment && previous === '*' && current === '/') { | ||
| 1552 | - isBlockComment = false; | ||
| 1553 | - } else if (isSingleComment && current === '\n') { | ||
| 1554 | - isSingleComment = false; | ||
| 1555 | - } else if (!isBlockComment && !isRegExpLiteral && !isSingleComment) { | ||
| 1556 | - if (current === '/' && previous === '/') { | ||
| 1557 | - isSingleComment = true; | ||
| 1558 | - } else if (previous === '/') { | ||
| 1559 | - if (current === '*') { | ||
| 1560 | - isBlockComment = true; | ||
| 1561 | - // Distinguish between a division operator and the start of a regex | ||
| 1562 | - // by examining the non-whitespace character that precedes the / | ||
| 1563 | - } else if ([null, '(', '[', '{', '}', ';'].includes(prevTokenChar)) { | ||
| 1564 | - isRegExpLiteral = true; | ||
| 1565 | - } | ||
| 1566 | - } else { | ||
| 1567 | - if (current.trim()) prevTokenChar = current; | ||
| 1568 | - if (current === '\'' || current === '"') { | ||
| 1569 | - stringLiteral = current; | ||
| 1570 | - } | ||
| 1571 | - } | ||
| 1572 | - } | ||
| 1573 | - } | ||
| 1574 | - | ||
| 1575 | - return stringLiteral ? lastChar === '\\' : isBlockComment; | ||
| 1576 | - } | ||
| 1577 | - | ||
| 1578 | 1510 | function Recoverable(err) { | |
| 1579 | 1511 | this.err = err; | |
| 1580 | 1512 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -146,6 +146,7 @@ | |||
| 146 | 146 | 'lib/internal/readline.js', | |
| 147 | 147 | 'lib/internal/repl.js', | |
| 148 | 148 | 'lib/internal/repl/await.js', | |
| 149 | + 'lib/internal/repl/recoverable.js', | ||
| 149 | 150 | 'lib/internal/socket_list.js', | |
| 150 | 151 | 'lib/internal/test/binding.js', | |
| 151 | 152 | 'lib/internal/test/heap.js', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -162,13 +162,11 @@ const errorTests = [ | |||
| 162 | 162 | // Template expressions | |
| 163 | 163 | { | |
| 164 | 164 | send: '`io.js ${"1.0"', | |
| 165 | - expect: [ | ||
| 166 | - kSource, | ||
| 167 | - kArrow, | ||
| 168 | - '', | ||
| 169 | - /^SyntaxError: /, | ||
| 170 | - '' | ||
| 171 | - ] | ||
| 165 | + expect: '... ' | ||
| 166 | + }, | ||
| 167 | + { | ||
| 168 | + send: '+ ".2"}`', | ||
| 169 | + expect: '\'io.js 1.0.2\'' | ||
| 172 | 170 | }, | |
| 173 | 171 | { | |
| 174 | 172 | send: '`io.js ${', | |
@@ -315,6 +313,15 @@ const errorTests = [ | |||
| 315 | 313 | send: '1 }', | |
| 316 | 314 | expect: '{ a: 1 }' | |
| 317 | 315 | }, | |
| 316 | + // Multiline string-keyed object (e.g. JSON) | ||
| 317 | + { | ||
| 318 | + send: '{ "a": ', | ||
| 319 | + expect: '... ' | ||
| 320 | + }, | ||
| 321 | + { | ||
| 322 | + send: '1 }', | ||
| 323 | + expect: '{ a: 1 }' | ||
| 324 | + }, | ||
| 318 | 325 | // Multiline anonymous function with comment | |
| 319 | 326 | { | |
| 320 | 327 | send: '(function() {', | |
| Back | FazBrowse Home | New Git URL |
0 commit comments