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.

index.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. // @ts-nocheck
  2. 'use strict';
  3. const _ = require('lodash');
  4. const beforeBlockString = require('../../utils/beforeBlockString');
  5. const hasBlock = require('../../utils/hasBlock');
  6. const optionsMatches = require('../../utils/optionsMatches');
  7. const report = require('../../utils/report');
  8. const ruleMessages = require('../../utils/ruleMessages');
  9. const styleSearch = require('style-search');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const ruleName = 'indentation';
  12. const messages = ruleMessages(ruleName, {
  13. expected: (x) => `Expected indentation of ${x}`,
  14. });
  15. /**
  16. * @param {number|"tab"} space - Number of whitespaces to expect, or else
  17. * keyword "tab" for single `\t`
  18. * @param {object} [options]
  19. */
  20. function rule(space, options = {}, context) {
  21. const isTab = space === 'tab';
  22. const indentChar = isTab ? '\t' : ' '.repeat(space);
  23. const warningWord = isTab ? 'tab' : 'space';
  24. return (root, result) => {
  25. const validOptions = validateOptions(
  26. result,
  27. ruleName,
  28. {
  29. actual: space,
  30. possible: [_.isNumber, 'tab'],
  31. },
  32. {
  33. actual: options,
  34. possible: {
  35. baseIndentLevel: [_.isNumber, 'auto'],
  36. except: ['block', 'value', 'param'],
  37. ignore: ['value', 'param', 'inside-parens'],
  38. indentInsideParens: ['twice', 'once-at-root-twice-in-block'],
  39. indentClosingBrace: [_.isBoolean],
  40. },
  41. optional: true,
  42. },
  43. );
  44. if (!validOptions) {
  45. return;
  46. }
  47. // Cycle through all nodes using walk.
  48. root.walk((node) => {
  49. if (node.type === 'root') {
  50. // Ignore nested template literals root in css-in-js lang
  51. return;
  52. }
  53. const nodeLevel = indentationLevel(node);
  54. // Cut out any * and _ hacks from `before`
  55. const before = (node.raws.before || '').replace(/[*_]$/, '');
  56. const after = node.raws.after || '';
  57. const parent = node.parent;
  58. const expectedOpeningBraceIndentation = indentChar.repeat(nodeLevel);
  59. // Only inspect the spaces before the node
  60. // if this is the first node in root
  61. // or there is a newline in the `before` string.
  62. // (If there is no newline before a node,
  63. // there is no "indentation" to check.)
  64. const isFirstChild = parent.type === 'root' && parent.first === node;
  65. const lastIndexOfNewline = before.lastIndexOf('\n');
  66. // Inspect whitespace in the `before` string that is
  67. // *after* the *last* newline character,
  68. // because anything besides that is not indentation for this node:
  69. // it is some other kind of separation, checked by some separate rule
  70. if (
  71. (lastIndexOfNewline !== -1 ||
  72. (isFirstChild && (!getDocument(parent) || parent.raws.beforeStart.endsWith('\n')))) &&
  73. before.slice(lastIndexOfNewline + 1) !== expectedOpeningBraceIndentation
  74. ) {
  75. if (context.fix) {
  76. if (isFirstChild && _.isString(node.raws.before)) {
  77. node.raws.before = node.raws.before.replace(
  78. /^[ \t]*(?=\S|$)/,
  79. expectedOpeningBraceIndentation,
  80. );
  81. }
  82. node.raws.before = fixIndentation(node.raws.before, expectedOpeningBraceIndentation);
  83. } else {
  84. report({
  85. message: messages.expected(legibleExpectation(nodeLevel)),
  86. node,
  87. result,
  88. ruleName,
  89. });
  90. }
  91. }
  92. // Only blocks have the `after` string to check.
  93. // Only inspect `after` strings that start with a newline;
  94. // otherwise there's no indentation involved.
  95. // And check `indentClosingBrace` to see if it should be indented an extra level.
  96. const closingBraceLevel = options.indentClosingBrace ? nodeLevel + 1 : nodeLevel;
  97. const expectedClosingBraceIndentation = indentChar.repeat(closingBraceLevel);
  98. if (
  99. hasBlock(node) &&
  100. after &&
  101. after.includes('\n') &&
  102. after.slice(after.lastIndexOf('\n') + 1) !== expectedClosingBraceIndentation
  103. ) {
  104. if (context.fix) {
  105. node.raws.after = fixIndentation(node.raws.after, expectedClosingBraceIndentation);
  106. } else {
  107. report({
  108. message: messages.expected(legibleExpectation(closingBraceLevel)),
  109. node,
  110. index: node.toString().length - 1,
  111. result,
  112. ruleName,
  113. });
  114. }
  115. }
  116. // If this is a declaration, check the value
  117. if (node.value) {
  118. checkValue(node, nodeLevel);
  119. }
  120. // If this is a rule, check the selector
  121. if (node.selector) {
  122. checkSelector(node, nodeLevel);
  123. }
  124. // If this is an at rule, check the params
  125. if (node.type === 'atrule') {
  126. checkAtRuleParams(node, nodeLevel);
  127. }
  128. });
  129. function indentationLevel(node, level = 0) {
  130. if (node.parent.type === 'root') {
  131. return level + getRootBaseIndentLevel(node.parent, options.baseIndentLevel, space);
  132. }
  133. let calculatedLevel;
  134. // Indentation level equals the ancestor nodes
  135. // separating this node from root; so recursively
  136. // run this operation
  137. calculatedLevel = indentationLevel(node.parent, level + 1);
  138. // If options.except includes "block",
  139. // blocks are taken down one from their calculated level
  140. // (all blocks are the same level as their parents)
  141. if (
  142. optionsMatches(options, 'except', 'block') &&
  143. (node.type === 'rule' || node.type === 'atrule') &&
  144. hasBlock(node)
  145. ) {
  146. calculatedLevel--;
  147. }
  148. return calculatedLevel;
  149. }
  150. function checkValue(decl, declLevel) {
  151. if (!decl.value.includes('\n')) {
  152. return;
  153. }
  154. if (optionsMatches(options, 'ignore', 'value')) {
  155. return;
  156. }
  157. const declString = decl.toString();
  158. const valueLevel = optionsMatches(options, 'except', 'value') ? declLevel : declLevel + 1;
  159. checkMultilineBit(declString, valueLevel, decl);
  160. }
  161. function checkSelector(ruleNode, ruleLevel) {
  162. const selector = ruleNode.selector;
  163. // Less mixins have params, and they should be indented extra
  164. if (ruleNode.params) {
  165. ruleLevel += 1;
  166. }
  167. checkMultilineBit(selector, ruleLevel, ruleNode);
  168. }
  169. function checkAtRuleParams(atRule, ruleLevel) {
  170. if (optionsMatches(options, 'ignore', 'param')) {
  171. return;
  172. }
  173. // @nest and SCSS's @at-root rules should be treated like regular rules, not expected
  174. // to have their params (selectors) indented
  175. const paramLevel =
  176. optionsMatches(options, 'except', 'param') ||
  177. atRule.name === 'nest' ||
  178. atRule.name === 'at-root'
  179. ? ruleLevel
  180. : ruleLevel + 1;
  181. checkMultilineBit(beforeBlockString(atRule).trim(), paramLevel, atRule);
  182. }
  183. function checkMultilineBit(source, newlineIndentLevel, node) {
  184. if (!source.includes('\n')) {
  185. return;
  186. }
  187. // Data for current node fixing
  188. const fixPositions = [];
  189. // `outsideParens` because function arguments and also non-standard parenthesized stuff like
  190. // Sass maps are ignored to allow for arbitrary indentation
  191. let parentheticalDepth = 0;
  192. styleSearch(
  193. {
  194. source,
  195. target: '\n',
  196. outsideParens: optionsMatches(options, 'ignore', 'inside-parens'),
  197. },
  198. (match, matchCount) => {
  199. const precedesClosingParenthesis = /^[ \t]*\)/.test(source.slice(match.startIndex + 1));
  200. if (
  201. optionsMatches(options, 'ignore', 'inside-parens') &&
  202. (precedesClosingParenthesis || match.insideParens)
  203. ) {
  204. return;
  205. }
  206. let expectedIndentLevel = newlineIndentLevel;
  207. // Modififications for parenthetical content
  208. if (!optionsMatches(options, 'ignore', 'inside-parens') && match.insideParens) {
  209. // If the first match in is within parentheses, reduce the parenthesis penalty
  210. if (matchCount === 1) parentheticalDepth -= 1;
  211. // Account for windows line endings
  212. let newlineIndex = match.startIndex;
  213. if (source[match.startIndex - 1] === '\r') {
  214. newlineIndex--;
  215. }
  216. const followsOpeningParenthesis = /\([ \t]*$/.test(source.slice(0, newlineIndex));
  217. if (followsOpeningParenthesis) {
  218. parentheticalDepth += 1;
  219. }
  220. const followsOpeningBrace = /{[ \t]*$/.test(source.slice(0, newlineIndex));
  221. if (followsOpeningBrace) {
  222. parentheticalDepth += 1;
  223. }
  224. const startingClosingBrace = /^[ \t]*}/.test(source.slice(match.startIndex + 1));
  225. if (startingClosingBrace) {
  226. parentheticalDepth -= 1;
  227. }
  228. expectedIndentLevel += parentheticalDepth;
  229. // Past this point, adjustments to parentheticalDepth affect next line
  230. if (precedesClosingParenthesis) {
  231. parentheticalDepth -= 1;
  232. }
  233. switch (options.indentInsideParens) {
  234. case 'twice':
  235. if (!precedesClosingParenthesis || options.indentClosingBrace) {
  236. expectedIndentLevel += 1;
  237. }
  238. break;
  239. case 'once-at-root-twice-in-block':
  240. if (node.parent === node.root()) {
  241. if (precedesClosingParenthesis && !options.indentClosingBrace) {
  242. expectedIndentLevel -= 1;
  243. }
  244. break;
  245. }
  246. if (!precedesClosingParenthesis || options.indentClosingBrace) {
  247. expectedIndentLevel += 1;
  248. }
  249. break;
  250. default:
  251. if (precedesClosingParenthesis && !options.indentClosingBrace) {
  252. expectedIndentLevel -= 1;
  253. }
  254. }
  255. }
  256. // Starting at the index after the newline, we want to
  257. // check that the whitespace characters (excluding newlines) before the first
  258. // non-whitespace character equal the expected indentation
  259. const afterNewlineSpaceMatches = /^([ \t]*)\S/.exec(source.slice(match.startIndex + 1));
  260. if (!afterNewlineSpaceMatches) {
  261. return;
  262. }
  263. const afterNewlineSpace = afterNewlineSpaceMatches[1];
  264. const expectedIndentation = indentChar.repeat(
  265. expectedIndentLevel > 0 ? expectedIndentLevel : 0,
  266. );
  267. if (afterNewlineSpace !== expectedIndentation) {
  268. if (context.fix) {
  269. // Adding fixes position in reverse order, because if we change indent in the beginning of the string it will break all following fixes for that string
  270. fixPositions.unshift({
  271. expectedIndentation,
  272. currentIndentation: afterNewlineSpace,
  273. startIndex: match.startIndex,
  274. });
  275. } else {
  276. report({
  277. message: messages.expected(legibleExpectation(expectedIndentLevel)),
  278. node,
  279. index: match.startIndex + afterNewlineSpace.length + 1,
  280. result,
  281. ruleName,
  282. });
  283. }
  284. }
  285. },
  286. );
  287. if (fixPositions.length) {
  288. if (node.type === 'rule') {
  289. fixPositions.forEach((fixPosition) => {
  290. node.selector = replaceIndentation(
  291. node.selector,
  292. fixPosition.currentIndentation,
  293. fixPosition.expectedIndentation,
  294. fixPosition.startIndex,
  295. );
  296. });
  297. }
  298. if (node.type === 'decl') {
  299. const declProp = node.prop;
  300. const declBetween = node.raws.between;
  301. fixPositions.forEach((fixPosition) => {
  302. if (fixPosition.startIndex < declProp.length + declBetween.length) {
  303. node.raws.between = replaceIndentation(
  304. declBetween,
  305. fixPosition.currentIndentation,
  306. fixPosition.expectedIndentation,
  307. fixPosition.startIndex - declProp.length,
  308. );
  309. } else {
  310. node.value = replaceIndentation(
  311. node.value,
  312. fixPosition.currentIndentation,
  313. fixPosition.expectedIndentation,
  314. fixPosition.startIndex - declProp.length - declBetween.length,
  315. );
  316. }
  317. });
  318. }
  319. if (node.type === 'atrule') {
  320. const atRuleName = node.name;
  321. const atRuleAfterName = node.raws.afterName;
  322. const atRuleParams = node.params;
  323. fixPositions.forEach((fixPosition) => {
  324. // 1 — it's a @ length
  325. if (fixPosition.startIndex < 1 + atRuleName.length + atRuleAfterName.length) {
  326. node.raws.afterName = replaceIndentation(
  327. atRuleAfterName,
  328. fixPosition.currentIndentation,
  329. fixPosition.expectedIndentation,
  330. fixPosition.startIndex - atRuleName.length - 1,
  331. );
  332. } else {
  333. node.params = replaceIndentation(
  334. atRuleParams,
  335. fixPosition.currentIndentation,
  336. fixPosition.expectedIndentation,
  337. fixPosition.startIndex - atRuleName.length - atRuleAfterName.length - 1,
  338. );
  339. }
  340. });
  341. }
  342. }
  343. }
  344. };
  345. function legibleExpectation(level) {
  346. const count = isTab ? level : level * space;
  347. const quantifiedWarningWord = count === 1 ? warningWord : `${warningWord}s`;
  348. return `${count} ${quantifiedWarningWord}`;
  349. }
  350. }
  351. function getRootBaseIndentLevel(root, baseIndentLevel, space) {
  352. const document = getDocument(root);
  353. if (!document) {
  354. return 0;
  355. }
  356. let indentLevel = root.source.baseIndentLevel;
  357. if (!Number.isSafeInteger(indentLevel)) {
  358. indentLevel = inferRootIndentLevel(root, baseIndentLevel, () =>
  359. inferDocIndentSize(document, space),
  360. );
  361. root.source.baseIndentLevel = indentLevel;
  362. }
  363. return indentLevel;
  364. }
  365. function getDocument(node) {
  366. const document = node.document;
  367. if (document) {
  368. return document;
  369. }
  370. const root = node.root();
  371. return root && root.document;
  372. }
  373. function inferDocIndentSize(document, space) {
  374. let indentSize = document.source.indentSize;
  375. if (Number.isSafeInteger(indentSize)) {
  376. return indentSize;
  377. }
  378. const source = document.source.input.css;
  379. const indents = source.match(/^ *(?=\S)/gm);
  380. if (indents) {
  381. const scores = {};
  382. let lastIndentSize = 0;
  383. let lastLeadingSpacesLength = 0;
  384. const vote = (leadingSpacesLength) => {
  385. if (leadingSpacesLength) {
  386. lastIndentSize = Math.abs(leadingSpacesLength - lastLeadingSpacesLength) || lastIndentSize;
  387. if (lastIndentSize > 1) {
  388. if (scores[lastIndentSize]) {
  389. scores[lastIndentSize]++;
  390. } else {
  391. scores[lastIndentSize] = 1;
  392. }
  393. }
  394. } else {
  395. lastIndentSize = 0;
  396. }
  397. lastLeadingSpacesLength = leadingSpacesLength;
  398. };
  399. indents.forEach((leadingSpaces) => {
  400. vote(leadingSpaces.length);
  401. });
  402. let bestScore = 0;
  403. for (const indentSizeDate in scores) {
  404. if (Object.prototype.hasOwnProperty.call(scores, indentSizeDate)) {
  405. const score = scores[indentSizeDate];
  406. if (score > bestScore) {
  407. bestScore = score;
  408. indentSize = indentSizeDate;
  409. }
  410. }
  411. }
  412. }
  413. indentSize = Number(indentSize) || (indents && indents[0].length) || Number(space) || 2;
  414. document.source.indentSize = indentSize;
  415. return indentSize;
  416. }
  417. function inferRootIndentLevel(root, baseIndentLevel, indentSize) {
  418. function getIndentLevel(indent) {
  419. let tabCount = indent.match(/\t/g);
  420. tabCount = tabCount ? tabCount.length : 0;
  421. let spaceCount = indent.match(/ /g);
  422. spaceCount = spaceCount ? Math.round(spaceCount.length / indentSize()) : 0;
  423. return tabCount + spaceCount;
  424. }
  425. if (!Number.isSafeInteger(baseIndentLevel)) {
  426. let source = root.source.input.css;
  427. source = source.replace(/^[^\r\n]+/, (firstLine) => {
  428. if (/(?:^|\n)([ \t]*)$/.test(root.raws.beforeStart)) {
  429. return RegExp.$1 + firstLine;
  430. }
  431. return '';
  432. });
  433. const indents = source.match(/^[ \t]*(?=\S)/gm);
  434. if (indents) {
  435. return Math.min(...indents.map(getIndentLevel));
  436. }
  437. baseIndentLevel = 1;
  438. }
  439. const indents = [];
  440. const foundIndents = /(?:^|\n)([ \t]*)\S[^\r\n]*(?:\r?\n\s*)*$/m.exec(root.raws.beforeStart);
  441. // The indent level of the CSS code block in non-CSS-like files is determined by the shortest indent of non-empty line.
  442. if (foundIndents) {
  443. let shortest = Number.MAX_SAFE_INTEGER;
  444. let i = 0;
  445. while (++i < foundIndents.length) {
  446. const current = getIndentLevel(foundIndents[i]);
  447. if (current < shortest) {
  448. shortest = current;
  449. if (shortest === 0) {
  450. break;
  451. }
  452. }
  453. }
  454. if (shortest !== Number.MAX_SAFE_INTEGER) {
  455. indents.push(new Array(shortest).fill(' ').join(''));
  456. }
  457. }
  458. const after = root.raws.after;
  459. if (after) {
  460. let afterEnd;
  461. if (after.endsWith('\n')) {
  462. const document = root.document;
  463. if (document) {
  464. const nextRoot = document.nodes[document.nodes.indexOf(root) + 1];
  465. afterEnd = nextRoot ? nextRoot.raws.beforeStart : document.raws.afterEnd;
  466. } else {
  467. // Nested root node in css-in-js lang
  468. const parent = root.parent;
  469. const nextRoot = parent.nodes[parent.nodes.indexOf(root) + 1];
  470. afterEnd = nextRoot ? nextRoot.raws.beforeStart : root.raws.afterEnd;
  471. }
  472. } else {
  473. afterEnd = after;
  474. }
  475. indents.push(afterEnd.match(/^[ \t]*/)[0]);
  476. }
  477. if (indents.length) {
  478. return Math.max(...indents.map(getIndentLevel)) + baseIndentLevel;
  479. }
  480. return baseIndentLevel;
  481. }
  482. function fixIndentation(str, whitespace) {
  483. if (!_.isString(str)) {
  484. return str;
  485. }
  486. return str.replace(/\n[ \t]*(?=\S|$)/g, `\n${whitespace}`);
  487. }
  488. function replaceIndentation(input, searchString, replaceString, startIndex) {
  489. const offset = startIndex + 1;
  490. const stringStart = input.slice(0, offset);
  491. const stringEnd = input.slice(offset + searchString.length);
  492. return stringStart + replaceString + stringEnd;
  493. }
  494. rule.ruleName = ruleName;
  495. rule.messages = messages;
  496. module.exports = rule;