summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ide/lib/editorconfig/parser.js
blob: 1597e4a8bfa4f615cd36adada14d9219a859201e (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
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)));
}