Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

checkExamples.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _eslint = require("eslint");
  7. var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc"));
  8. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  9. // Todo: When peerDeps bump to ESLint 7, see about replacing `CLIEngine`
  10. // with non-deprecated `ESLint` class:
  11. // https://github.com/eslint/eslint/blob/master/docs/user-guide/migrating-to-7.0.0.md#-the-cliengine-class-has-been-deprecated
  12. const zeroBasedLineIndexAdjust = -1;
  13. const likelyNestedJSDocIndentSpace = 1;
  14. const preTagSpaceLength = 1; // If a space is present, we should ignore it
  15. const firstLinePrefixLength = preTagSpaceLength;
  16. const hasCaptionRegex = /^\s*<caption>([\s\S]*?)<\/caption>/u;
  17. const escapeStringRegexp = str => {
  18. return str.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
  19. };
  20. const countChars = (str, ch) => {
  21. return (str.match(new RegExp(escapeStringRegexp(ch), 'gu')) || []).length;
  22. };
  23. const defaultMdRules = {
  24. // "always" newline rule at end unlikely in sample code
  25. 'eol-last': 0,
  26. // Wouldn't generally expect example paths to resolve relative to JS file
  27. 'import/no-unresolved': 0,
  28. // Snippets likely too short to always include import/export info
  29. 'import/unambiguous': 0,
  30. 'jsdoc/require-file-overview': 0,
  31. // The end of a multiline comment would end the comment the example is in.
  32. 'jsdoc/require-jsdoc': 0,
  33. // Unlikely to have inadvertent debugging within examples
  34. 'no-console': 0,
  35. // Often wish to start `@example` code after newline; also may use
  36. // empty lines for spacing
  37. 'no-multiple-empty-lines': 0,
  38. // Many variables in examples will be `undefined`
  39. 'no-undef': 0,
  40. // Common to define variables for clarity without always using them
  41. 'no-unused-vars': 0,
  42. // See import/no-unresolved
  43. 'node/no-missing-import': 0,
  44. 'node/no-missing-require': 0,
  45. // Can generally look nicer to pad a little even if code imposes more stringency
  46. 'padded-blocks': 0
  47. };
  48. const defaultExpressionRules = { ...defaultMdRules,
  49. 'chai-friendly/no-unused-expressions': 'off',
  50. 'no-empty-function': 'off',
  51. 'no-new': 'off',
  52. 'no-unused-expressions': 'off',
  53. quotes: ['error', 'double'],
  54. semi: ['error', 'never'],
  55. strict: 'off'
  56. };
  57. const getLinesCols = text => {
  58. const matchLines = countChars(text, '\n');
  59. const colDelta = matchLines ? text.slice(text.lastIndexOf('\n') + 1).length : text.length;
  60. return [matchLines, colDelta];
  61. };
  62. var _default = (0, _iterateJsdoc.default)(({
  63. report,
  64. utils,
  65. context,
  66. globalState
  67. }) => {
  68. if (!globalState.has('checkExamples-matchingFileName')) {
  69. globalState.set('checkExamples-matchingFileName', new Map());
  70. }
  71. const matchingFileNameMap = globalState.get('checkExamples-matchingFileName');
  72. const options = context.options[0] || {};
  73. let {
  74. exampleCodeRegex = null,
  75. rejectExampleCodeRegex = null
  76. } = options;
  77. const {
  78. checkDefaults = false,
  79. checkParams = false,
  80. checkProperties = false,
  81. noDefaultExampleRules = false,
  82. checkEslintrc = true,
  83. matchingFileName = null,
  84. matchingFileNameDefaults = null,
  85. matchingFileNameParams = null,
  86. matchingFileNameProperties = null,
  87. paddedIndent = 0,
  88. baseConfig = {},
  89. configFile,
  90. allowInlineConfig = true,
  91. reportUnusedDisableDirectives = true,
  92. captionRequired = false
  93. } = options; // Make this configurable?
  94. const rulePaths = [];
  95. const mdRules = noDefaultExampleRules ? undefined : defaultMdRules;
  96. const expressionRules = noDefaultExampleRules ? undefined : defaultExpressionRules;
  97. if (exampleCodeRegex) {
  98. exampleCodeRegex = utils.getRegexFromString(exampleCodeRegex);
  99. }
  100. if (rejectExampleCodeRegex) {
  101. rejectExampleCodeRegex = utils.getRegexFromString(rejectExampleCodeRegex);
  102. }
  103. const checkSource = ({
  104. filename,
  105. defaultFileName,
  106. rules = expressionRules,
  107. lines = 0,
  108. cols = 0,
  109. skipInit,
  110. source,
  111. targetTagName,
  112. sources = [],
  113. tag = {
  114. line: 0
  115. }
  116. }) => {
  117. if (!skipInit) {
  118. sources.push({
  119. nonJSPrefacingCols: cols,
  120. nonJSPrefacingLines: lines,
  121. string: source
  122. });
  123. } // Todo: Make fixable
  124. const checkRules = function ({
  125. nonJSPrefacingCols,
  126. nonJSPrefacingLines,
  127. string
  128. }) {
  129. const cliConfig = {
  130. allowInlineConfig,
  131. baseConfig,
  132. configFile,
  133. reportUnusedDisableDirectives,
  134. rulePaths,
  135. rules,
  136. useEslintrc: checkEslintrc
  137. };
  138. const cliConfigStr = JSON.stringify(cliConfig);
  139. const src = paddedIndent ? string.replace(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'gu'), '\n') : string; // Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
  140. const fileNameMapKey = filename ? 'a' + cliConfigStr + filename : 'b' + cliConfigStr + defaultFileName;
  141. const file = filename || defaultFileName;
  142. let cliFile;
  143. if (matchingFileNameMap.has(fileNameMapKey)) {
  144. cliFile = matchingFileNameMap.get(fileNameMapKey);
  145. } else {
  146. const cli = new _eslint.CLIEngine(cliConfig);
  147. let config;
  148. if (filename || checkEslintrc) {
  149. config = cli.getConfigForFile(file);
  150. } // We need a new instance to ensure that the rules that may only
  151. // be available to `file` (if it has its own `.eslintrc`),
  152. // will be defined.
  153. cliFile = new _eslint.CLIEngine({
  154. allowInlineConfig,
  155. baseConfig: { ...baseConfig,
  156. ...config
  157. },
  158. configFile,
  159. reportUnusedDisableDirectives,
  160. rulePaths,
  161. rules,
  162. useEslintrc: false
  163. });
  164. matchingFileNameMap.set(fileNameMapKey, cliFile);
  165. }
  166. const {
  167. results: [{
  168. messages
  169. }]
  170. } = cliFile.executeOnText(src);
  171. if (!('line' in tag)) {
  172. tag.line = tag.source[0].number;
  173. } // NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
  174. const codeStartLine = tag.line + nonJSPrefacingLines;
  175. const codeStartCol = likelyNestedJSDocIndentSpace;
  176. messages.forEach(({
  177. message,
  178. line,
  179. column,
  180. severity,
  181. ruleId
  182. }) => {
  183. const startLine = codeStartLine + line + zeroBasedLineIndexAdjust;
  184. const startCol = codeStartCol + ( // This might not work for line 0, but line 0 is unlikely for examples
  185. line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength) + column;
  186. report('@' + targetTagName + ' ' + (severity === 2 ? 'error' : 'warning') + (ruleId ? ' (' + ruleId + ')' : '') + ': ' + message, null, {
  187. column: startCol,
  188. line: startLine
  189. });
  190. });
  191. };
  192. sources.forEach(checkRules);
  193. };
  194. /**
  195. *
  196. * @param {string} filename
  197. * @param {string} [ext="md/*.js"] Since `eslint-plugin-markdown` v2, and
  198. * ESLint 7, this is the default which other JS-fenced rules will used.
  199. * Formerly "md" was the default.
  200. * @returns {{defaultFileName: string, fileName: string}}
  201. */
  202. const getFilenameInfo = (filename, ext = 'md/*.js') => {
  203. let defaultFileName;
  204. if (!filename) {
  205. const jsFileName = context.getFilename();
  206. if (typeof jsFileName === 'string' && jsFileName.includes('.')) {
  207. defaultFileName = jsFileName.replace(/\..*?$/, `.${ext}`);
  208. } else {
  209. defaultFileName = `dummy.${ext}`;
  210. }
  211. }
  212. return {
  213. defaultFileName,
  214. filename
  215. };
  216. };
  217. if (checkDefaults) {
  218. const filenameInfo = getFilenameInfo(matchingFileNameDefaults, 'jsdoc-defaults');
  219. utils.forEachPreferredTag('default', (tag, targetTagName) => {
  220. if (!tag.description.trim()) {
  221. return;
  222. }
  223. checkSource({
  224. source: `(${utils.getTagDescription(tag)})`,
  225. targetTagName,
  226. ...filenameInfo
  227. });
  228. });
  229. }
  230. if (checkParams) {
  231. const filenameInfo = getFilenameInfo(matchingFileNameParams, 'jsdoc-params');
  232. utils.forEachPreferredTag('param', (tag, targetTagName) => {
  233. if (!tag.default || !tag.default.trim()) {
  234. return;
  235. }
  236. checkSource({
  237. source: `(${tag.default})`,
  238. targetTagName,
  239. ...filenameInfo
  240. });
  241. });
  242. }
  243. if (checkProperties) {
  244. const filenameInfo = getFilenameInfo(matchingFileNameProperties, 'jsdoc-properties');
  245. utils.forEachPreferredTag('property', (tag, targetTagName) => {
  246. if (!tag.default || !tag.default.trim()) {
  247. return;
  248. }
  249. checkSource({
  250. source: `(${tag.default})`,
  251. targetTagName,
  252. ...filenameInfo
  253. });
  254. });
  255. }
  256. const tagName = utils.getPreferredTagName({
  257. tagName: 'example'
  258. });
  259. if (!utils.hasTag(tagName)) {
  260. return;
  261. }
  262. const matchingFilenameInfo = getFilenameInfo(matchingFileName);
  263. utils.forEachPreferredTag('example', (tag, targetTagName) => {
  264. let source = utils.getTagDescription(tag);
  265. const match = source.match(hasCaptionRegex);
  266. if (captionRequired && (!match || !match[1].trim())) {
  267. report('Caption is expected for examples.', null, tag);
  268. }
  269. source = source.replace(hasCaptionRegex, '');
  270. const [lines, cols] = match ? getLinesCols(match[0]) : [0, 0];
  271. if (exampleCodeRegex && !exampleCodeRegex.test(source) || rejectExampleCodeRegex && rejectExampleCodeRegex.test(source)) {
  272. return;
  273. }
  274. const sources = [];
  275. let skipInit = false;
  276. if (exampleCodeRegex) {
  277. let nonJSPrefacingCols = 0;
  278. let nonJSPrefacingLines = 0;
  279. let startingIndex = 0;
  280. let lastStringCount = 0;
  281. let exampleCode;
  282. exampleCodeRegex.lastIndex = 0;
  283. while ((exampleCode = exampleCodeRegex.exec(source)) !== null) {
  284. const {
  285. index,
  286. 0: n0,
  287. 1: n1
  288. } = exampleCode; // Count anything preceding user regex match (can affect line numbering)
  289. const preMatch = source.slice(startingIndex, index);
  290. const [preMatchLines, colDelta] = getLinesCols(preMatch);
  291. let nonJSPreface;
  292. let nonJSPrefaceLineCount;
  293. if (n1) {
  294. const idx = n0.indexOf(n1);
  295. nonJSPreface = n0.slice(0, idx);
  296. nonJSPrefaceLineCount = countChars(nonJSPreface, '\n');
  297. } else {
  298. nonJSPreface = '';
  299. nonJSPrefaceLineCount = 0;
  300. }
  301. nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount; // Ignore `preMatch` delta if newlines here
  302. if (nonJSPrefaceLineCount) {
  303. const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length;
  304. nonJSPrefacingCols += charsInLastLine;
  305. } else {
  306. nonJSPrefacingCols += colDelta + nonJSPreface.length;
  307. }
  308. const string = n1 || n0;
  309. sources.push({
  310. nonJSPrefacingCols,
  311. nonJSPrefacingLines,
  312. string
  313. });
  314. startingIndex = exampleCodeRegex.lastIndex;
  315. lastStringCount = countChars(string, '\n');
  316. if (!exampleCodeRegex.global) {
  317. break;
  318. }
  319. }
  320. skipInit = true;
  321. }
  322. checkSource({
  323. cols,
  324. lines,
  325. rules: mdRules,
  326. skipInit,
  327. source,
  328. sources,
  329. tag,
  330. targetTagName,
  331. ...matchingFilenameInfo
  332. });
  333. });
  334. }, {
  335. iterateAllJsdocs: true,
  336. meta: {
  337. docs: {
  338. description: 'Ensures that (JavaScript) examples within JSDoc adhere to ESLint rules.',
  339. url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-check-examples'
  340. },
  341. schema: [{
  342. additionalProperties: false,
  343. properties: {
  344. allowInlineConfig: {
  345. default: true,
  346. type: 'boolean'
  347. },
  348. baseConfig: {
  349. type: 'object'
  350. },
  351. captionRequired: {
  352. default: false,
  353. type: 'boolean'
  354. },
  355. checkDefaults: {
  356. default: false,
  357. type: 'boolean'
  358. },
  359. checkEslintrc: {
  360. default: true,
  361. type: 'boolean'
  362. },
  363. checkParams: {
  364. default: false,
  365. type: 'boolean'
  366. },
  367. checkProperties: {
  368. default: false,
  369. type: 'boolean'
  370. },
  371. configFile: {
  372. type: 'string'
  373. },
  374. exampleCodeRegex: {
  375. type: 'string'
  376. },
  377. matchingFileName: {
  378. type: 'string'
  379. },
  380. matchingFileNameDefaults: {
  381. type: 'string'
  382. },
  383. matchingFileNameParams: {
  384. type: 'string'
  385. },
  386. matchingFileNameProperties: {
  387. type: 'string'
  388. },
  389. noDefaultExampleRules: {
  390. default: false,
  391. type: 'boolean'
  392. },
  393. paddedIndent: {
  394. default: 0,
  395. type: 'integer'
  396. },
  397. rejectExampleCodeRegex: {
  398. type: 'string'
  399. },
  400. reportUnusedDisableDirectives: {
  401. default: true,
  402. type: 'boolean'
  403. }
  404. },
  405. type: 'object'
  406. }],
  407. type: 'suggestion'
  408. }
  409. });
  410. exports.default = _default;
  411. module.exports = exports.default;
  412. //# sourceMappingURL=checkExamples.js.map