Skip to content

Commit e80f74b

Browse files
committed
Update editorconfig
1 parent 68cd299 commit e80f74b

3 files changed

Lines changed: 60 additions & 147 deletions

File tree

lib/utils/editorconfig.js

Lines changed: 4 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,17 @@
11
'use strict';
22

3-
const fs = require('fs');
4-
const path = require('path');
3+
const editorconfig = require('editorconfig');
54

65
/**
7-
* Lightweight .editorconfig parser.
8-
*
9-
* Walks up from `filePath` collecting .editorconfig files, parses their
10-
* INI-like sections, and returns the merged properties that apply.
11-
*
12-
* Only simple glob patterns are supported:
13-
* * – matches everything
14-
* *.ext – matches files with that extension
15-
* *.{a,b} – matches files with extension a or b
16-
* [section] – literal pattern
17-
*
18-
* This intentionally does NOT replicate the full editorconfig-core spec
19-
* (no ** path globs, no numeric ranges, etc.) because for indent_size
20-
* resolution the simple patterns cover virtually all real configs.
21-
*/
22-
23-
const COMMENT_RE = /^\s*[#;]/;
24-
const SECTION_RE = /^\s*\[(.+?)]\s*$/;
25-
const PROPERTY_RE = /^\s*([\w.-]+)\s*=\s*(.*?)\s*$/;
26-
27-
function parseEditorConfig(contents) {
28-
const sections = [];
29-
let current = { glob: null, props: {} }; // preamble (root, etc.)
30-
sections.push(current);
31-
32-
for (const rawLine of contents.split(/\r?\n/)) {
33-
const line = rawLine.trim();
34-
if (!line || COMMENT_RE.test(line)) {
35-
continue;
36-
}
37-
const sectionMatch = SECTION_RE.exec(line);
38-
if (sectionMatch) {
39-
current = { glob: sectionMatch[1], props: {} };
40-
sections.push(current);
41-
continue;
42-
}
43-
const propMatch = PROPERTY_RE.exec(line);
44-
if (propMatch) {
45-
const key = propMatch[1].toLowerCase();
46-
let value = propMatch[2].toLowerCase();
47-
// Parse numbers and booleans
48-
if (/^\d+$/.test(value)) {
49-
value = Number(value);
50-
} else if (value === 'true') {
51-
value = true;
52-
} else if (value === 'false') {
53-
value = false;
54-
}
55-
current.props[key] = value;
56-
}
57-
}
58-
return sections;
59-
}
60-
61-
/**
62-
* Tests whether a simple editorconfig glob matches a filename (basename only).
63-
*/
64-
function globMatchesFilename(glob, filename) {
65-
// Strip leading path separators (editorconfig globs without / apply to basename)
66-
if (glob.includes('/')) {
67-
// Path-style globs are not supported in this lightweight impl
68-
return false;
69-
}
70-
if (glob === '*') {
71-
return true;
72-
}
73-
// Handle *.{ext1,ext2} and *.ext
74-
const braceMatch = /^\*\.{([^}]+)}$/.exec(glob);
75-
if (braceMatch) {
76-
const extensions = braceMatch[1].split(',').map((e) => e.trim());
77-
return extensions.some((ext) => filename.endsWith(`.${ext}`));
78-
}
79-
if (glob.startsWith('*.')) {
80-
const ext = glob.slice(1); // e.g. ".hbs"
81-
return filename.endsWith(ext);
82-
}
83-
// Literal match
84-
return filename === glob;
85-
}
86-
87-
/**
88-
* Collect .editorconfig files from `dir` up to the filesystem root,
89-
* stopping at a file that declares `root = true`.
90-
*/
91-
function collectConfigFiles(dir) {
92-
const files = [];
93-
let current = dir;
94-
// eslint-disable-next-line no-constant-condition
95-
while (true) {
96-
const configPath = path.join(current, '.editorconfig');
97-
try {
98-
const contents = fs.readFileSync(configPath, 'utf8');
99-
const sections = parseEditorConfig(contents);
100-
const isRoot = sections[0] && sections[0].glob === null && sections[0].props.root === true;
101-
files.push({ dir: current, sections });
102-
if (isRoot) {
103-
break;
104-
}
105-
} catch {
106-
// No .editorconfig at this level, keep going
107-
}
108-
const parent = path.dirname(current);
109-
if (parent === current) {
110-
break;
111-
}
112-
current = parent;
113-
}
114-
return files;
115-
}
116-
117-
/**
118-
* Resolve editorconfig properties for a given file path.
6+
* Resolve editorconfig properties for a given file path using the official
7+
* editorconfig library.
1198
*
1209
* Returns an object like `{ indent_size: 4, indent_style: 'space', ... }`
12110
* with only the properties that matched. Returns an empty object if no
12211
* .editorconfig is found or no sections match.
12312
*/
12413
function resolveEditorConfig(filePath) {
125-
const dir = path.dirname(filePath);
126-
const filename = path.basename(filePath);
127-
const configFiles = collectConfigFiles(dir);
128-
129-
// Merge: outermost first, innermost wins (same as editorconfig spec)
130-
const merged = {};
131-
for (let i = configFiles.length - 1; i >= 0; i--) {
132-
for (const section of configFiles[i].sections) {
133-
if (section.glob === null) {
134-
continue; // preamble section (root = true, etc.)
135-
}
136-
if (globMatchesFilename(section.glob, filename)) {
137-
Object.assign(merged, section.props);
138-
}
139-
}
140-
}
141-
142-
// Apply editorconfig post-processing rules
143-
if (merged.indent_style === 'tab' && merged.indent_size === undefined) {
144-
merged.indent_size = 'tab';
145-
}
146-
if (
147-
merged.indent_size !== undefined &&
148-
merged.tab_width === undefined &&
149-
merged.indent_size !== 'tab'
150-
) {
151-
merged.tab_width = merged.indent_size;
152-
}
153-
if (merged.indent_size === 'tab' && merged.tab_width !== undefined) {
154-
merged.indent_size = merged.tab_width;
155-
}
156-
157-
return merged;
14+
return editorconfig.parseSync(filePath);
15815
}
15916

16017
module.exports = { resolveEditorConfig };

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"@ember-data/rfc395-data": "^0.0.4",
6363
"aria-query": "^5.3.2",
6464
"css-tree": "^3.0.1",
65+
"editorconfig": "^3.0.2",
6566
"ember-eslint-parser": "^0.6.0",
6667
"ember-rfc176-data": "^0.3.18",
6768
"eslint-utils": "^3.0.0",

pnpm-lock.yaml

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)