@cspell/eslint-plugin
Version:
358 lines • 13.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.spellCheckAST = spellCheckAST;
// cspell:ignore TSESTree
const node_assert_1 = __importDefault(require("node:assert"));
const synckit_1 = require("synckit");
const logger_cjs_1 = require("../common/logger.cjs");
const customScopes_cjs_1 = require("./customScopes.cjs");
const scope_cjs_1 = require("./scope.cjs");
const walkTree_cjs_1 = require("./walkTree.cjs");
const spellCheck = (0, synckit_1.createSyncFn)(require.resolve('./worker.mjs'));
const isDebugModeExtended = false;
const forceLogging = false;
const skipCheck = process.env.CSPELL_ESLINT_SKIP_CHECK || false;
function spellCheckAST(filename, text, root, options) {
const logger = (0, logger_cjs_1.getDefaultLogger)();
const debugMode = forceLogging || options.debugMode || false;
logger.enabled = forceLogging || (options.debugMode ?? (logger.enabled || isDebugModeExtended));
const log = logger.log;
const mapScopes = groupScopes([...customScopes_cjs_1.defaultCheckedScopes, ...(options.checkScope || [])]);
log('options: %o', options);
const toIgnore = new Set();
const importedIdentifiers = new Set();
const toBeChecked = [];
function checkLiteral(path) {
const node = path.node;
if (node.type !== 'Literal')
return;
if (!options.checkStrings)
return;
if (typeof node.value === 'string') {
debugNode(path, node.value);
if (options.ignoreImports && (isImportOrRequired(node) || isExportNamedDeclaration(node)))
return;
if (options.ignoreImportProperties && isImportedProperty(node))
return;
checkNodeText(path, node.value);
}
}
function checkJSXText(path) {
const node = path.node;
if (node.type !== 'JSXText')
return;
if (!options.checkJSXText)
return;
if (typeof node.value === 'string') {
debugNode(path, node.value);
checkNodeText(path, node.value);
}
}
function checkTemplateElement(path) {
const node = path.node;
if (node.type !== 'TemplateElement')
return;
if (!options.checkStringTemplates)
return;
debugNode(path, node.value);
checkNodeText(path, node.value.cooked || node.value.raw);
}
function checkIdentifier(path) {
const node = path.node;
if (node.type !== 'Identifier')
return;
debugNode(path, node.name);
if (options.ignoreImports) {
if (isRawImportIdentifier(node)) {
toIgnore.add(node.name);
return;
}
if (isImportIdentifier(node)) {
importedIdentifiers.add(node.name);
if (isLocalImportIdentifierUnique(node)) {
checkNodeText(path, node.name);
}
return;
}
else if (options.ignoreImportProperties && isImportedProperty(node)) {
return;
}
if (isExportIdentifier(node)) {
importedIdentifiers.add(node.name);
if (isLocalExportIdentifierUnique(node)) {
checkNodeText(path, node.name);
}
return;
}
}
if (!options.checkIdentifiers)
return;
if (toIgnore.has(node.name) && !isObjectProperty(node))
return;
if (skipCheckForRawImportIdentifiers(node))
return;
checkNodeText(path, node.name);
}
function checkComment(path) {
const node = path.node;
if (node.type !== 'Line' && node.type !== 'Block')
return;
if (!options.checkComments)
return;
debugNode(path, node.value);
checkNodeText(path, node.value);
}
function checkNodeText(path, _text) {
const node = path.node;
if (!node.range)
return;
const adj = node.type === 'Literal' ? 1 : 0;
const range = [node.range[0] + adj, node.range[1] - adj];
toBeChecked.push({ range, node });
}
function isImportIdentifier(node) {
const parent = node.parent;
if (node.type !== 'Identifier' || !parent)
return false;
return ((parent.type === 'ImportSpecifier' ||
parent.type === 'ImportNamespaceSpecifier' ||
parent.type === 'ImportDefaultSpecifier') &&
parent.local === node);
}
function isExportIdentifier(node) {
const parent = getExportParent(node);
if (node.type !== 'Identifier' || !parent)
return false;
return parent.type === 'ExportSpecifier' && parent.exported === node;
}
function isRawImportIdentifier(node) {
const parent = node.parent;
if (node.type !== 'Identifier' || !parent)
return false;
return ((parent.type === 'ImportSpecifier' && parent.imported === node) ||
(parent.type === 'ExportSpecifier' && parent.local === node));
}
function isLocalImportIdentifierUnique(node) {
const parent = getImportParent(node);
if (!parent)
return true;
const { imported, local } = parent;
if (imported.type === 'Identifier' && imported.name !== local.name)
return true;
return imported.range?.[0] !== local.range?.[0] && imported.range?.[1] !== local.range?.[1];
}
function isLocalExportIdentifierUnique(node) {
const parent = getExportParent(node);
if (!parent)
return true;
const { exported, local } = parent;
if (exported.type === 'Identifier' && exported.name !== local.name)
return true;
return exported.range?.[0] !== local.range?.[0] && exported.range?.[1] !== local.range?.[1];
}
function getImportParent(node) {
const parent = node.parent;
return parent?.type === 'ImportSpecifier' ? parent : undefined;
}
function getExportParent(node) {
const parent = node.parent;
return parent?.type === 'ExportSpecifier' ? parent : undefined;
}
function skipCheckForRawImportIdentifiers(node) {
if (options.ignoreImports)
return false;
const parent = getImportParent(node);
return !!parent && parent.imported === node && !isLocalImportIdentifierUnique(node);
}
function isImportedProperty(node) {
const obj = findOriginObject(node);
return !!obj && obj.type === 'Identifier' && importedIdentifiers.has(obj.name);
}
function isObjectProperty(node) {
return node.parent?.type === 'MemberExpression';
}
function reportIssue(issue) {
const { word, start, end, severity } = issue;
const node = toBeChecked[issue.rangeIdx].node;
const nodeType = node.type;
const suggestions = normalizeSuggestions(issue.suggestions, nodeType);
return { word, start, end, nodeType, node, suggestions, severity };
}
const processors = {
Line: checkComment,
Block: checkComment,
Literal: checkLiteral,
TemplateElement: checkTemplateElement,
Identifier: checkIdentifier,
JSXText: checkJSXText,
};
function needToCheckFields(path) {
const possibleScopes = mapScopes.get(path.node.type);
if (!possibleScopes) {
if (debugMode)
_dumpNode(path);
return undefined;
}
const scopePath = new scope_cjs_1.AstPathScope(path);
const scores = possibleScopes
.map(({ scope, check }) => ({ score: scopePath.score(scope), check, scope }))
.filter((s) => s.score > 0);
const maxScore = Math.max(0, ...scores.map((s) => s.score));
const topScopes = scores.filter((s) => s.score === maxScore);
if (!topScopes.length)
return undefined;
return Object.fromEntries(topScopes.map((s) => [s.scope.scopeField(), s.check]));
}
function defaultHandler(path) {
const fields = needToCheckFields(path);
if (!fields)
return;
for (const [field, check] of Object.entries(fields)) {
if (!check)
continue;
const node = path.node;
const value = node[field];
if (typeof value !== 'string')
continue;
debugNode(path, value);
checkNodeText(path, value);
}
}
function checkNode(path) {
// _dumpNode(path);
const handler = processors[path.node.type] ?? defaultHandler;
handler(path);
}
function _dumpNode(path) {
function value(v) {
if (['string', 'number', 'boolean'].includes(typeof v))
return v;
if (v && typeof v === 'object' && 'type' in v)
return `{ type: ${v.type} }`;
return `<${v}>`;
}
function dotValue(v) {
if (typeof v === 'object' && v) {
return Object.fromEntries(Object.entries(v).map(([k, v]) => [k, value(v)]));
}
return `<${typeof v}>`;
}
const { parent: _, ...n } = path.node;
const warn = log;
warn('Node: %o', {
key: path.key,
type: n.type,
path: inheritanceSummary(path),
node: dotValue(n),
});
}
/**
* find the origin of a member expression
*/
function findOriginObject(node) {
const parent = node.parent;
if (parent?.type !== 'MemberExpression' || parent.property !== node)
return undefined;
let obj = parent.object;
while (obj.type === 'MemberExpression') {
obj = obj.object;
}
return obj;
}
function isFunctionCall(node, name) {
if (!node)
return false;
return node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === name;
}
function isRequireCall(node) {
return isFunctionCall(node, 'require');
}
function isImportOrRequired(node) {
return isRequireCall(node.parent) || (node.parent?.type === 'ImportDeclaration' && node.parent.source === node);
}
function isExportNamedDeclaration(node) {
return node.parent?.type === 'ExportNamedDeclaration' && node.parent.source === node;
}
function debugNode(path, value) {
if (!debugMode)
return;
log(`${inheritanceSummary(path)}: %o`, value);
_dumpNode(path);
}
// console.warn('root: %o', root);
(0, walkTree_cjs_1.walkTree)(root, checkNode);
const result = skipCheck
? { issues: [] }
: spellCheck(filename, text, toBeChecked.map((t) => t.range), options);
const issues = result.issues.map((issue) => reportIssue(issue));
return { issues, errors: result.errors || [] };
}
function mapNode(path, key) {
const node = path.node;
if (node.type === 'Literal') {
return (0, scope_cjs_1.scopeItem)(tagLiteral(node));
}
if (node.type === 'Block') {
const value = typeof node.value === 'string' ? node.value : '';
return (0, scope_cjs_1.scopeItem)(value[0] === '*' ? 'Comment.docBlock' : 'Comment.block');
}
if (node.type === 'Line') {
return (0, scope_cjs_1.scopeItem)('Comment.line');
}
return (0, scope_cjs_1.mapNodeToScope)(path, key);
}
function inheritanceSummary(path) {
return (0, scope_cjs_1.astScopeToString)(path, ' ', mapNode);
}
function tagLiteral(node) {
(0, node_assert_1.default)(node.type === 'Literal');
const kind = typeof node.value;
const extra = kind === 'string'
? asStr(node.raw)?.[0] === '"'
? 'string.double'
: 'string.single'
: node.value === null
? 'null'
: kind;
return node.type + '.' + extra;
}
const needToAdjustSpace = {
Identifier: true,
};
const isSpecial = /[^\p{L}_0-9]/u;
const allSpecial = /[^\p{L}_0-9]/gu;
function normalizeSuggestions(suggestions, nodeType) {
if (!suggestions)
return undefined;
if (!(nodeType in needToAdjustSpace))
return suggestions;
return suggestions.map((sug) => {
if (!isSpecial.test(sug.word))
return sug;
const s = { ...sug };
s.word = s.word.replaceAll(allSpecial, '_');
if (s.wordAdjustedToMatchCase) {
s.wordAdjustedToMatchCase = s.wordAdjustedToMatchCase.replaceAll(allSpecial, '_');
}
return s;
});
}
function groupScopes(scopes) {
const objScopes = Object.fromEntries(scopes);
const map = new Map();
for (const [selector, check] of Object.entries(objScopes)) {
const scope = scope_cjs_1.AstScopeMatcher.fromScopeSelector(selector);
const key = scope.scopeType();
const list = map.get(key) || [];
list.push({ scope, check });
map.set(key, list);
}
return map;
}
function asStr(v) {
return typeof v === 'string' ? v : undefined;
}
//# sourceMappingURL=spellCheckAST.cjs.map