"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _eslint = require("eslint"); var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc")); var _warnRemovedSettings = _interopRequireDefault(require("../warnRemovedSettings")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } const zeroBasedLineIndexAdjust = -1; const likelyNestedJSDocIndentSpace = 1; const preTagSpaceLength = 1; // If a space is present, we should ignore it const firstLinePrefixLength = preTagSpaceLength; const hasCaptionRegex = /^\s*(.*?)<\/caption>/u; const escapeStringRegexp = str => { return str.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); }; const countChars = (str, ch) => { return (str.match(new RegExp(escapeStringRegexp(ch), 'gu')) || []).length; }; const getRegexFromString = regexString => { const match = regexString.match(/^\/(.*)\/([gimyus]*)$/u); let flags = 'u'; let regex = regexString; if (match) { var _match = _slicedToArray(match, 3); regex = _match[1]; flags = _match[2]; if (!flags) { flags = 'u'; } const uniqueFlags = [...new Set(flags)]; flags = uniqueFlags.join(''); } return new RegExp(regex, flags); }; var _default = (0, _iterateJsdoc.default)(({ report, utils, context, globalState }) => { (0, _warnRemovedSettings.default)(context, 'check-examples'); const tagName = utils.getPreferredTagName({ tagName: 'example' }); if (!utils.hasTag(tagName)) { return; } if (!globalState.has('checkExamples-matchingFileName')) { globalState.set('checkExamples-matchingFileName', new Map()); } const matchingFileNameMap = globalState.get('checkExamples-matchingFileName'); const options = context.options[0] || {}; let _options$exampleCodeR = options.exampleCodeRegex, exampleCodeRegex = _options$exampleCodeR === void 0 ? null : _options$exampleCodeR, _options$rejectExampl = options.rejectExampleCodeRegex, rejectExampleCodeRegex = _options$rejectExampl === void 0 ? null : _options$rejectExampl; const _options$noDefaultExa = options.noDefaultExampleRules, noDefaultExampleRules = _options$noDefaultExa === void 0 ? false : _options$noDefaultExa, _options$checkEslintr = options.checkEslintrc, checkEslintrc = _options$checkEslintr === void 0 ? true : _options$checkEslintr, _options$matchingFile = options.matchingFileName, filename = _options$matchingFile === void 0 ? null : _options$matchingFile, _options$paddedIndent = options.paddedIndent, paddedIndent = _options$paddedIndent === void 0 ? 0 : _options$paddedIndent, _options$baseConfig = options.baseConfig, baseConfig = _options$baseConfig === void 0 ? {} : _options$baseConfig, configFile = options.configFile, _options$allowInlineC = options.allowInlineConfig, allowInlineConfig = _options$allowInlineC === void 0 ? true : _options$allowInlineC, _options$reportUnused = options.reportUnusedDisableDirectives, reportUnusedDisableDirectives = _options$reportUnused === void 0 ? true : _options$reportUnused, _options$captionRequi = options.captionRequired, captionRequired = _options$captionRequi === void 0 ? false : _options$captionRequi; let defaultFileName; if (!filename) { const jsFileName = context.getFilename(); if (typeof jsFileName === 'string' && jsFileName.includes('.')) { defaultFileName = jsFileName.replace(/\..*?$/, '.md'); } else { defaultFileName = 'dummy.md'; } } // Make this configurable? const rulePaths = []; const rules = noDefaultExampleRules ? undefined : { // "always" newline rule at end unlikely in sample code 'eol-last': 0, // Wouldn't generally expect example paths to resolve relative to JS file 'import/no-unresolved': 0, // Snippets likely too short to always include import/export info 'import/unambiguous': 0, // Unlikely to have inadvertent debugging within examples 'no-console': 0, // Often wish to start `@example` code after newline; also may use // empty lines for spacing 'no-multiple-empty-lines': 0, // Many variables in examples will be `undefined` 'no-undef': 0, // Common to define variables for clarity without always using them 'no-unused-vars': 0, // See import/no-unresolved 'node/no-missing-import': 0, 'node/no-missing-require': 0, // Can generally look nicer to pad a little even if code imposes more stringency 'padded-blocks': 0 }; if (exampleCodeRegex) { exampleCodeRegex = getRegexFromString(exampleCodeRegex); } if (rejectExampleCodeRegex) { rejectExampleCodeRegex = getRegexFromString(rejectExampleCodeRegex); } utils.forEachPreferredTag('example', (tag, targetTagName) => { let source = tag.description; const match = source.match(hasCaptionRegex); if (captionRequired && (!match || !match[1].trim())) { report('Caption is expected for examples.', null, tag); } // If we allow newlines in hasCaptionRegex, we should add to line count source = source.replace(hasCaptionRegex, ''); if (exampleCodeRegex && !exampleCodeRegex.test(source) || rejectExampleCodeRegex && rejectExampleCodeRegex.test(source)) { return; } const sources = []; if (exampleCodeRegex) { let nonJSPrefacingCols = 0; let nonJSPrefacingLines = 0; let startingIndex = 0; let lastStringCount = 0; let exampleCode; exampleCodeRegex.lastIndex = 0; while ((exampleCode = exampleCodeRegex.exec(source)) !== null) { const _exampleCode = exampleCode, index = _exampleCode.index, n0 = _exampleCode[0], n1 = _exampleCode[1]; // Count anything preceding user regex match (can affect line numbering) const preMatch = source.slice(startingIndex, index); const preMatchLines = countChars(preMatch, '\n'); const colDelta = preMatchLines ? preMatch.slice(preMatch.lastIndexOf('\n') + 1).length : preMatch.length; let nonJSPreface; let nonJSPrefaceLineCount; if (n1) { const idx = n0.indexOf(n1); nonJSPreface = n0.slice(0, idx); nonJSPrefaceLineCount = countChars(nonJSPreface, '\n'); } else { nonJSPreface = ''; nonJSPrefaceLineCount = 0; } nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount; // Ignore `preMatch` delta if newlines here if (nonJSPrefaceLineCount) { const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length; nonJSPrefacingCols += charsInLastLine; } else { nonJSPrefacingCols += colDelta + nonJSPreface.length; } const string = n1 || n0; sources.push({ nonJSPrefacingCols, nonJSPrefacingLines, string }); startingIndex = exampleCodeRegex.lastIndex; lastStringCount = countChars(string, '\n'); if (!exampleCodeRegex.global) { break; } } } else { sources.push({ nonJSPrefacingCols: 0, nonJSPrefacingLines: 0, string: source }); } // Todo: Make fixable // Todo: Fix whitespace indent const checkRules = function checkRules({ nonJSPrefacingCols, nonJSPrefacingLines, string }) { const cliConfig = { allowInlineConfig, baseConfig, configFile, reportUnusedDisableDirectives, rulePaths, rules, useEslintrc: checkEslintrc }; const cliConfigStr = JSON.stringify(cliConfig); const src = paddedIndent ? string.replace(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'gu'), '\n') : string; // Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api const fileNameMapKey = filename ? 'a' + cliConfigStr + filename : 'b' + cliConfigStr + defaultFileName; const file = filename || defaultFileName; let cliFile; if (matchingFileNameMap.has(fileNameMapKey)) { cliFile = matchingFileNameMap.get(fileNameMapKey); } else { const cli = new _eslint.CLIEngine(cliConfig); let config; if (filename || checkEslintrc) { config = cli.getConfigForFile(file); } // We need a new instance to ensure that the rules that may only // be available to `file` (if it has its own `.eslintrc`), // will be defined. cliFile = new _eslint.CLIEngine({ allowInlineConfig, baseConfig: _objectSpread({}, baseConfig, {}, config), configFile, reportUnusedDisableDirectives, rulePaths, rules, useEslintrc: false }); matchingFileNameMap.set(fileNameMapKey, cliFile); } const _cliFile$executeOnTex = cliFile.executeOnText(src), _cliFile$executeOnTex2 = _slicedToArray(_cliFile$executeOnTex.results, 1), messages = _cliFile$executeOnTex2[0].messages; // NOTE: `tag.line` can be 0 if of form `/** @tag ... */` const codeStartLine = tag.line + nonJSPrefacingLines; const codeStartCol = likelyNestedJSDocIndentSpace; messages.forEach(({ message, line, column, severity, ruleId }) => { const startLine = codeStartLine + line + zeroBasedLineIndexAdjust; const startCol = codeStartCol + ( // This might not work for line 0, but line 0 is unlikely for examples line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength) + column; report('@' + targetTagName + ' ' + (severity === 2 ? 'error' : 'warning') + (ruleId ? ' (' + ruleId + ')' : '') + ': ' + message, null, { column: startCol, line: startLine }); }); }; sources.forEach(checkRules); }); }, { iterateAllJsdocs: true, meta: { schema: [{ additionalProperties: false, properties: { allowInlineConfig: { default: true, type: 'boolean' }, baseConfig: { type: 'object' }, captionRequired: { default: false, type: 'boolean' }, checkEslintrc: { default: true, type: 'boolean' }, configFile: { type: 'string' }, exampleCodeRegex: { type: 'string' }, matchingFileName: { type: 'string' }, noDefaultExampleRules: { default: false, type: 'boolean' }, paddedIndent: { default: 0, type: 'integer' }, rejectExampleCodeRegex: { type: 'string' }, reportUnusedDisableDirectives: { default: true, type: 'boolean' } }, type: 'object' }], type: 'suggestion' }, noTrim: true }); exports.default = _default; module.exports = exports.default; //# sourceMappingURL=checkExamples.js.map