diff --git a/lib/6to5/helpers/levenshtein.js b/lib/6to5/helpers/levenshtein.js new file mode 100644 index 0000000000..e20d4455b5 --- /dev/null +++ b/lib/6to5/helpers/levenshtein.js @@ -0,0 +1,38 @@ +// not the fastest + +module.exports = function (a, b) { + if (a.length === 0) return b.length; + if (b.length === 0) return a.length; + + var matrix = []; + + // increment along the first column of each row + var i; + for(i = 0; i <= b.length; i++){ + matrix[i] = [i]; + } + + // increment each column in the first row + var j; + for(j = 0; j <= a.length; j++){ + matrix[0][j] = j; + } + + // Fill in the rest of the matrix + for(i = 1; i <= b.length; i++){ + for(j = 1; j <= a.length; j++){ + if(b.charAt(i - 1) == a.charAt(j - 1)){ + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, // substitution + Math.min( + matrix[i][j - 1] + 1, // insertion + matrix[i - 1][j] + 1) // deletion + ); + } + } + } + + return matrix[b.length][a.length]; +} diff --git a/lib/6to5/transformation/transformers/validation/undeclared-variable-check.js b/lib/6to5/transformation/transformers/validation/undeclared-variable-check.js index 338da22569..af3b9e88f4 100644 --- a/lib/6to5/transformation/transformers/validation/undeclared-variable-check.js +++ b/lib/6to5/transformation/transformers/validation/undeclared-variable-check.js @@ -1,9 +1,38 @@ "use strict"; +var levenshtein = require("../../../helpers/levenshtein"); +var t = require("../../../types"); + exports.optional = true; exports.Identifier = function (node, parent, scope, context, file) { - if (!scope.has(node.name, true)) { - throw file.errorWithNode(node, "Reference to undeclared variable", ReferenceError); + if (!t.isReferenced(node, parent)) return; + if (scope.has(node.name, true)) return; + + var msg = "Reference to undeclared variable"; + + // get the closest declaration to offer as a suggestion + // the variable name may have just been mistyped + + var declarations = scope.getAllDeclarations(); + + var closest; + var shortest = -1; + + for (var name in declarations) { + var distance = levenshtein(node.name, name); + if (distance <= 0 || distance > 3) continue; + if (distance <= shortest) continue; + + closest = name; + shortest = distance; } + + if (closest) { + msg += " - Did you mean " + closest + "?"; + } + + // + + throw file.errorWithNode(node, msg, ReferenceError); }; diff --git a/lib/6to5/traverse/scope.js b/lib/6to5/traverse/scope.js index 5143b9bfba..cfa859fb78 100644 --- a/lib/6to5/traverse/scope.js +++ b/lib/6to5/traverse/scope.js @@ -326,12 +326,31 @@ Scope.prototype.getFunctionParent = function () { }; /** - * Walks the scope tree and gathers all declarations of `kind`. + * Walks the scope tree and gathers **all** declarations. * * @returns {Object} */ -Scope.prototype.getAllOfKind = function (kind) { +Scope.prototype.getAllDeclarations = function () { + var ids = object(); + + var scope = this; + do { + defaults(ids, scope.declarations); + scope = scope.parent; + } while (scope); + + return ids; +}; + +/** + * Walks the scope tree and gathers all declarations of `kind`. + * + * @param {String} kind + * @returns {Object} + */ + +Scope.prototype.getAllDeclarationsOfKind = function (kind) { var ids = object(); var scope = this;