1/* 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const fs = require('fs'); 17const path = require('path'); 18const { hasAPINote, getAPINote, overwriteIndexOf, ErrorType, ErrorLevel, FileType, requireTypescriptModule } = require('./utils'); 19const { addAPICheckErrorLogs } = require('./compile_info'); 20const rules = require('../code_style_rule.json'); 21const dictionariesContent = fs.readFileSync(path.resolve(__dirname, '../plugin/dictionaries.txt'), 'utf-8'); 22const dictionariesArr = dictionariesContent.split(/[(\r\n)\r\n]+/g); 23const dictionariesSupplementaryContent = fs.readFileSync(path.resolve(__dirname, 24 '../plugin/dictionaries_supplementary.txt'), 'utf-8'); 25const dictionariesSupplementaryArr = dictionariesSupplementaryContent.split(/[(\r\n)\r\n]+/g); 26const dictionariesSet = new Set([...dictionariesArr, ...dictionariesSupplementaryArr, ...rules.decorators.customDoc, 27 ...rules.decorators.jsDoc]); 28const ts = requireTypescriptModule(); 29 30function checkSpelling(node, sourcefile, fileName) { 31 if (ts.isIdentifier(node) && node.escapedText) { 32 checkWordSpelling(node.escapedText.toString(), node, sourcefile, fileName, FileType.API); 33 } else if (hasAPINote(node)) { 34 const apiNote = getAPINote(node); 35 const words = splitParagraph(apiNote); 36 words.forEach(word => { 37 checkWordSpelling(word, node, sourcefile, fileName, FileType.JSDOC); 38 }); 39 } 40} 41exports.checkSpelling = checkSpelling; 42 43function checkWordSpelling(nodeText, node, sourcefile, fileName, type) { 44 const basicWords = splitComplexWords(nodeText); 45 const errorWords = []; 46 const suggest = []; 47 basicWords.forEach(word => { 48 if (!checkBaseWord(word.toLowerCase())) { 49 errorWords.push(word); 50 } 51 }); 52 if (errorWords.length !== 0) { 53 errorWords.forEach(errorWord => { 54 const levArr = []; 55 for (let i = 0; i < dictionariesArr.length; i++) { 56 const dictionary = dictionariesArr[i]; 57 levArr.push(getLevenshteinValue(errorWord, dictionary)); 58 } 59 const minLev = Math.min(...levArr); 60 const indexArr = overwriteIndexOf(minLev, levArr); 61 const MAX_LENGTH = 5; 62 for (let i = 0; i < indexArr.length; i++) { 63 if (i === MAX_LENGTH) { 64 break; 65 } 66 suggest.push(dictionariesArr[indexArr[i]]); 67 } 68 }); 69 const errorInfo = `Error words in [${nodeText}]: {${errorWords}}.Do you want to spell it as [${suggest}]?`; 70 addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.MISSPELL_WORDS, errorInfo, type, ErrorLevel.MIDDLE); 71 } 72} 73 74/** 75 * check base word 76 * @param {string} word 77 * @return true or false 78**/ 79function checkBaseWord(word) { 80 if (/^[a-z][0-9]+$/g.test(word)) { 81 return false; 82 } else if (/^[A-Za-z]+/g.test(word) && !dictionariesSet.has(word.toLowerCase())) { 83 return false; 84 } 85 return true; 86} 87 88function splitComplexWords(complexWord) { 89 let basicWords = []; 90 // splite underlineWord 91 if (hasUnderline(complexWord)) { 92 basicWords = complexWord.split(/(?<!^)\_/g); 93 } else { 94 // splite complexWord 95 if (!/(?<!^)(?=[A-Z])/g.test(complexWord)) { 96 basicWords.push(complexWord); 97 } else { 98 basicWords = complexWord.split(/(?<!^)(?=[A-Z])/g); 99 } 100 } 101 const newBaseWords = []; 102 basicWords.forEach(word => { 103 if (/[0-9]/g.test(word)) { 104 newBaseWords.concat(word.split(/0-9/g)); 105 } else { 106 newBaseWords.push(word); 107 }; 108 }); 109 return newBaseWords; 110} 111exports.splitComplexWords = splitComplexWords; 112 113function hasUnderline(word) { 114 return /(?<!^)\_/g.test(word); 115} 116exports.hasUnderline = hasUnderline; 117 118function splitParagraph(paragraph) { 119 const splitParagraphRegex = /[^\w]/g; 120 const words = paragraph.split(splitParagraphRegex); 121 return words; 122} 123exports.splitParagraph = splitParagraph; 124 125// Levenshtein method 126function getLevenshteinValue(word1, word2) { 127 const word1Len = word1.length; 128 const word2Len = word2.length; 129 130 if (word1Len * word2Len === 0) { 131 return Math.max(word1Len, word2Len); 132 } 133 // create Levenshtein two-dimensional array 134 const levArr = Array.from(new Array(word1Len + 1), () => new Array(word2Len + 1)); 135 // set value 0 to Levenshtein two-dimensional array 136 for (let i = 0; i < word1Len + 1; i++) { 137 for (let j = 0; j < word2Len + 1; j++) { 138 levArr[i][j] = 0; 139 } 140 } 141 142 // init Levenshtein two-dimensional array 143 for (let i = 0; i < word1Len + 1; i++) { 144 levArr[i][0] = i; 145 } 146 for (let j = 0; j < word2Len + 1; j++) { 147 levArr[0][j] = j; 148 } 149 150 // calculate levinstein distance 151 for (let i = 1; i < word1Len + 1; i++) { 152 for (let j = 1; j < word2Len + 1; j++) { 153 const countByInsert = levArr[i][j - 1] + 1; 154 const countByDel = levArr[i - 1][j] + 1; 155 const countByReplace = word1.charAt(i - 1) === word2.charAt(j - 1) ? 156 levArr[i - 1][j - 1] : levArr[i - 1][j - 1] + 1; 157 levArr[i][j] = Math.min(countByInsert, countByDel, countByReplace); 158 } 159 } 160 161 return levArr[word1Len][word2Len]; 162} 163exports.getLevenshteinValue = getLevenshteinValue; 164