UNPKG

@cspell/eslint-plugin

Version:
165 lines 6.4 kB
// cspell:ignore TSESTree import assert from 'node:assert'; import * as path from 'node:path'; import { toFileDirURL, toFileURL } from '@cspell/url'; import { createTextDocument, DocumentValidator, extractImportErrors, getDictionary, refreshDictionaryCache, } from 'cspell-lib'; import { getDefaultLogger } from '../common/logger.cjs'; const defaultSettings = { name: 'eslint-configuration-file', patterns: [ // @todo: be able to use cooked / transformed strings. // { // // Do not block unicode escape sequences. // name: 'js-unicode-escape', // pattern: /$^/g, // }, ], }; const isDebugModeExtended = false; const forceLogging = false; const knownConfigErrors = new Set(); export async function spellCheck(filename, text, ranges, options) { const logger = getDefaultLogger(); const debugMode = forceLogging || options.debugMode || false; logger.enabled = forceLogging || (options.debugMode ?? (logger.enabled || isDebugModeExtended)); const log = logger.log; log('options: %o', options); const validator = getDocValidator(filename, text, options); await validator.prepare(); log('Settings: %o', validator.settings); const errors = [...validator.errors]; errors.push(...(await checkSettings())); const issues = ranges .map((range, idx) => { const issues = validator .checkText(range, undefined, undefined) .map((issue) => normalizeIssue(issue, range, idx)); return issues.length ? issues : undefined; }) .filter((issues) => !!issues) .flat(); return { issues, errors }; async function checkSettings() { const finalSettings = validator.getFinalizedDocSettings(); const found = await reportConfigurationErrors(finalSettings, knownConfigErrors); found.forEach((err) => (debugMode ? log(err) : log('Error: %s', err.message))); return found; } function normalizeIssue(issue, range, rangeIdx) { const word = issue.text; const start = issue.offset; const end = issue.offset + (issue.length || issue.text.length); const suggestions = issue.suggestionsEx; const severity = issue.isFlagged ? 'Forbidden' : 'Unknown'; return { word, start, end, suggestions, severity, range, rangeIdx }; } } const cache = { lastDoc: undefined }; const docValCache = new WeakMap(); function getDocValidator(filename, text, options) { const doc = getTextDocument(filename, text); const settings = calcInitialSettings(options); const cachedValidator = docValCache.get(doc); if (cachedValidator && deepEqual(cachedValidator.settings, settings)) { refreshDictionaryCache(0); cachedValidator.updateDocumentText(text).catch(() => undefined); return cachedValidator; } const resolveImportsRelativeTo = toFileURL(options.cspellOptionsRoot || import.meta.url, toFileDirURL(options.cwd)); const validator = new DocumentValidator(doc, { ...options, resolveImportsRelativeTo }, settings); docValCache.set(doc, validator); return validator; } function calcInitialSettings(options) { const { customWordListFile, cspell, cwd } = options; const settings = { ...defaultSettings, ...cspell, words: cspell?.words || [], ignoreWords: cspell?.ignoreWords || [], flagWords: cspell?.flagWords || [], }; if (options.configFile) { const optionCspellImport = options.cspell?.import; const importConfig = typeof optionCspellImport === 'string' ? [optionCspellImport] : Array.isArray(optionCspellImport) ? optionCspellImport : []; importConfig.push(options.configFile); settings.import = importConfig; } if (customWordListFile) { const filePath = isCustomWordListFile(customWordListFile) ? customWordListFile.path : customWordListFile; const { dictionaries = [], dictionaryDefinitions = [] } = settings; dictionaries.push('eslint-plugin-custom-words'); dictionaryDefinitions.push({ name: 'eslint-plugin-custom-words', path: filePath }); settings.dictionaries = dictionaries; settings.dictionaryDefinitions = dictionaryDefinitions; } resolveDictionaryPaths(settings.dictionaryDefinitions, cwd); return settings; } const regexIsUrl = /^(https?|file|ftp):/i; /** Patches the path of dictionary definitions. */ function resolveDictionaryPaths(defs, cwd) { if (!defs) return; for (const def of defs) { if (!def.path) continue; if (regexIsUrl.test(def.path)) continue; def.path = path.resolve(cwd, def.path); } } function getTextDocument(filename, content) { if (cache.lastDoc?.filename === filename) { return cache.lastDoc.doc; } const doc = createTextDocument({ uri: filename, content }); cache.lastDoc = { filename, doc }; return doc; } function isCustomWordListFile(value) { return !!value && typeof value === 'object'; } /** * Deep Equal check. * Note: There are faster methods, but this is called once per file, so speed is not a concern. */ function deepEqual(a, b) { try { assert.deepStrictEqual(a, b); return true; } catch { return false; } } async function reportConfigurationErrors(config, knownConfigErrors) { const errors = []; const importErrors = extractImportErrors(config); importErrors.forEach((ref) => { const key = ref.error.toString(); if (knownConfigErrors.has(key)) return; knownConfigErrors.add(key); errors.push(new Error('Configuration Error: \n ' + ref.error.message)); }); const dictCollection = await getDictionary(config); dictCollection.dictionaries.forEach((dict) => { const dictErrors = dict.getErrors?.() || []; const msg = `Dictionary Error with (${dict.name})`; dictErrors.forEach((error) => { const key = msg + error.toString(); if (knownConfigErrors.has(key)) return; knownConfigErrors.add(key); const errMsg = `${msg}: ${error.message}\n Source: ${dict.source}`; errors.push(new Error(errMsg)); }); }); return errors; } //# sourceMappingURL=spellCheck.mjs.map