summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ide/lib/editorconfig/parser.js
blob: 2adc643a15b5a0d1b7ccede9ce87d0afffe6044e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { parseString } from 'editorconfig/src/lib/ini';
import minimatch from 'minimatch';
import { getPathParents } from '../../utils';

const dirname = (path) => path.replace(/\.editorconfig$/, '');

function isRootConfig(config) {
  return config.some(([pattern, rules]) => !pattern && rules?.root === 'true');
}

function getRulesForSection(path, [pattern, rules]) {
  if (!pattern) {
    return {};
  }
  if (minimatch(path, pattern, { matchBase: true })) {
    return rules;
  }

  return {};
}

function getRulesWithConfigs(filePath, configFiles = [], rules = {}) {
  if (!configFiles.length) return rules;

  const [{ content, path: configPath }, ...nextConfigs] = configFiles;
  const configDir = dirname(configPath);

  if (!filePath.startsWith(configDir)) return rules;

  const parsed = parseString(content);
  const isRoot = isRootConfig(parsed);
  const relativeFilePath = filePath.slice(configDir.length);

  const sectionRules = parsed.reduce(
    (acc, section) => Object.assign(acc, getRulesForSection(relativeFilePath, section)),
    {},
  );

  // prefer existing rules by overwriting to section rules
  const result = Object.assign(sectionRules, rules);

  return isRoot ? result : getRulesWithConfigs(filePath, nextConfigs, result);
}

export function getRulesWithTraversal(filePath, getFileContent) {
  const editorconfigPaths = [
    ...getPathParents(filePath).map((x) => `${x}/.editorconfig`),
    '.editorconfig',
  ];

  return Promise.all(
    editorconfigPaths.map((path) => getFileContent(path).then((content) => ({ path, content }))),
  ).then((results) =>
    getRulesWithConfigs(
      filePath,
      results.filter((x) => x.content),
    ),
  );
}