From 0fe502b77f67f619faac1577594f581c3d54fa94 Mon Sep 17 00:00:00 2001 From: Aaron Harvey Date: Fri, 14 Oct 2016 10:43:51 -0400 Subject: [PATCH] fix: annotation spell check, better @noflow support --- src/rules/requireValidFileAnnotation.js | 34 ++++++------- src/utilities/fuzzyStringMatch.js | 47 ++++++++++++++++++ src/utilities/index.js | 1 + src/utilities/isFlowFile.js | 2 +- .../assertions/requireValidFileAnnotation.js | 48 ++++++++++++++++--- 5 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/utilities/fuzzyStringMatch.js diff --git a/src/rules/requireValidFileAnnotation.js b/src/rules/requireValidFileAnnotation.js index 20874467..a998432e 100644 --- a/src/rules/requireValidFileAnnotation.js +++ b/src/rules/requireValidFileAnnotation.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import { - isFlowFile, - isFlowFileAnnotation + isFlowFileAnnotation, + fuzzyStringMatch } from './../utilities'; const defaults = { @@ -9,7 +9,7 @@ const defaults = { }; const looksLikeFlowFileAnnotation = (comment) => { - return /@(?:no)?flow/i.test(comment); + return /@(?:no)?f/i.test(comment); }; const isValidAnnotationStyle = (node, style) => { @@ -20,6 +20,10 @@ const isValidAnnotationStyle = (node, style) => { return style === node.type.toLowerCase(); }; +const checkAnnotationSpelling = (comment) => { + return /@[a-z]+\b/.test(comment) && fuzzyStringMatch(comment.replace(/no/i, ''), '@flow', 0.20); +}; + export const schema = [ { enum: ['always'] @@ -27,13 +31,7 @@ export const schema = [ ]; export default (context) => { - const checkThisFile = !_.get(context, 'settings.flowtype.onlyFilesWithFlowAnnotation') || isFlowFile(context); - - if (!checkThisFile) { - return {}; - } - - const always = context.options[0] === 'always'; + const always = context.options[0] === 'always' && !_.get(context, 'settings.flowtype.onlyFilesWithFlowAnnotation', false); const style = _.get(context, 'options[1].annotationStyle', defaults.annotationStyle); return { @@ -49,14 +47,16 @@ export default (context) => { context.report(potentialFlowFileAnnotation, 'Flow file annotation not at the top of the file.'); } - if (!isFlowFileAnnotation(potentialFlowFileAnnotation.value)) { - context.report(potentialFlowFileAnnotation, 'Malformed flow file annotation.'); - } - - if (!isValidAnnotationStyle(potentialFlowFileAnnotation, style)) { - const str = style === 'line' ? '`// @flow`' : '`/* @flow */`'; + if (isFlowFileAnnotation(potentialFlowFileAnnotation.value.trim())) { + if (!isValidAnnotationStyle(potentialFlowFileAnnotation, style)) { + const str = style === 'line' ? '`// ' + potentialFlowFileAnnotation.value.trim() + '`' : '`/* ' + potentialFlowFileAnnotation.value.trim() + ' */`'; - context.report(potentialFlowFileAnnotation, 'Flow file annotation style must be ' + str); + context.report(potentialFlowFileAnnotation, 'Flow file annotation style must be ' + str); + } + } else if (checkAnnotationSpelling(potentialFlowFileAnnotation.value.trim())) { + context.report(potentialFlowFileAnnotation, 'Misspelled or malformed Flow file annotation.'); + } else { + context.report(potentialFlowFileAnnotation, 'Malformed Flow file annotation.'); } } else if (always) { context.report(node, 'Flow file annotation is missing.'); diff --git a/src/utilities/fuzzyStringMatch.js b/src/utilities/fuzzyStringMatch.js new file mode 100644 index 00000000..4a42fb80 --- /dev/null +++ b/src/utilities/fuzzyStringMatch.js @@ -0,0 +1,47 @@ + +import _ from 'lodash'; +/** + * Creates an array of letter pairs from a given an array + * https://github.com/d3/d3-array/blob/master/src/pairs.js + * + * @param {any} array + * @returns array + */ +/* eslint-disable */ +function d3ArrayPairs (array) { + var i = 0, n = array.length - 1, p = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [p, p = array[++i]]; + return pairs; +}; +/* eslint-enable */ + +export default (needle, haystack, weight = 0.5) => { + // Based on http://stackoverflow.com/a/23305385 + + const stringSimilarity = (str1, str2) => { + if (str1.length > 0 && str2.length > 0) { + const pairs1 = d3ArrayPairs(str1); + const pairs2 = d3ArrayPairs(str2); + const unionLen = pairs1.length + pairs2.length; + let hitCount; + + hitCount = 0; + + _.forIn(pairs1, (val1) => { + _.forIn(pairs2, (val2) => { + if (_.isEqual(val1, val2)) { + hitCount++; + } + }); + }); + + if (hitCount > 0) { + return 2.0 * hitCount / unionLen; + } + } + + return 0.0; + }; + + return stringSimilarity(needle, haystack) >= Number(weight); +}; diff --git a/src/utilities/index.js b/src/utilities/index.js index 3ef344d3..55ff992f 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -8,3 +8,4 @@ export * as spacingFixers from './spacingFixers'; export quoteName from './quoteName'; export getTokenBeforeParens from './getTokenBeforeParens'; export getTokenAfterParens from './getTokenAfterParens'; +export fuzzyStringMatch from './fuzzyStringMatch'; diff --git a/src/utilities/isFlowFile.js b/src/utilities/isFlowFile.js index 73660ff2..4e157c7f 100644 --- a/src/utilities/isFlowFile.js +++ b/src/utilities/isFlowFile.js @@ -9,5 +9,5 @@ export default (context) => { const firstComment = comments[0]; - return isFlowFileAnnotation(firstComment.value); + return isFlowFileAnnotation(firstComment.value) && !/no/.test(firstComment.value); }; diff --git a/tests/rules/assertions/requireValidFileAnnotation.js b/tests/rules/assertions/requireValidFileAnnotation.js index a47ee380..394cfd68 100644 --- a/tests/rules/assertions/requireValidFileAnnotation.js +++ b/tests/rules/assertions/requireValidFileAnnotation.js @@ -20,23 +20,31 @@ export default { code: '// @Flow', errors: [ { - message: 'Malformed flow file annotation.' + message: 'Malformed Flow file annotation.' } ] }, { - code: '// @floweeeeeee', + code: '// @NoFlow', errors: [ { - message: 'Malformed flow file annotation.' + message: 'Malformed Flow file annotation.' } ] }, { - code: '// @NoFlow', + code: '// @Noflow', errors: [ { - message: 'Malformed flow file annotation.' + message: 'Malformed Flow file annotation.' + } + ] + }, + { + code: '// @floweeeeeee', + errors: [ + { + message: 'Misspelled or malformed Flow file annotation.' } ] }, @@ -44,7 +52,7 @@ export default { code: '// @nofloweeeeeee', errors: [ { - message: 'Malformed flow file annotation.' + message: 'Misspelled or malformed Flow file annotation.' } ] }, @@ -86,6 +94,34 @@ export default { annotationStyle: 'block' } ] + }, + { + code: '/* @noflow */', + errors: [ + { + message: 'Flow file annotation style must be `// @noflow`' + } + ], + options: [ + 'always', + { + annotationStyle: 'line' + } + ] + }, + { + code: '// @noflow', + errors: [ + { + message: 'Flow file annotation style must be `/* @noflow */`' + } + ], + options: [ + 'always', + { + annotationStyle: 'block' + } + ] } ], valid: [