MoonEditorConfig

An explainable EditorConfig core and style diagnostics toolkit for MoonBit projects.

editorconfig
glob
formatter
diagnostics
moonbit
moon add tlhuecdkoyg/MoonEditorConfig@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
4 hours ago
Downloads
2

Dependencies

README

#MoonEditorConfig

MoonEditorConfig 是使用 MoonBit 编写的 EditorConfig 解析、验证、配置解析与 来源追踪工具。它既可以作为 MoonBit 库使用,也提供中文 CLI 和完全在本地运行的 可视化 Studio。

给定一个目标文件,MoonEditorConfig 会从目标目录向上发现 .editorconfig root = true 处停止,匹配每个配置节并计算最终属性。与只返回键值的实现不同, 它会保留每次设置、覆盖、派生和取消设置的来源。

#功能

  • 可恢复解析器:保留文件、行、列、偏移量和结构化诊断
  • EditorConfig Glob:***?、字符集合、字符范围、选择和数值范围
  • 跨平台路径归一化与父目录配置发现
  • 层级合并、root = trueunsetindent_size/tab_width 派生
  • 标准属性类型验证,同时保留编辑器自定义属性
  • 最终值、匹配节和完整覆盖历史解释
  • 配置质量评分、维护建议与重叠节冲突分析
  • 规范格式化、统一 Diff 和安全/需复核修复计划
  • 内存虚拟工作区、批量解析、属性分组与未覆盖文件检测
  • JSON 输出,适合 CI、编辑器和其他工具集成
  • 默认中文的 MoonEditorConfig Studio

#环境要求

  • MoonBit 0.1.20260814 或更新版本
  • 启动 Studio 时需要 Node.js;本项目环境已包含 D:\conda\environments\moonbit\node.exe

检查环境:

D:\conda\environments\moonbit\moonbit-toolchain\bin\moon.exe version --all

#快速开始

cd D:\conda\environments\moonbit\projects\MoonEditorConfig moon check --deny-warn moon test --deny-warn moon run cmd/main -- check examples/valid.editorconfig

如果当前终端找不到 moon,可直接使用完整路径:

D:\conda\environments\moonbit\moonbit-toolchain\bin\moon.exe test --deny-warn

#CLI

检查一个配置文件:

moon run cmd/main -- check path/to/.editorconfig

解析目标文件的最终配置;命令会自动向父目录发现 .editorconfig

moon run cmd/main -- resolve src/main.mbt moon run cmd/main -- resolve src/main.mbt --json

显示匹配和覆盖过程:

moon run cmd/main -- explain src/main.mbt

查询单个属性:

moon run cmd/main -- query src/main.mbt indent_size

测试 Glob:

moon run cmd/main -- match "src/**.mbt" "src/parser/main.mbt"

格式化、预览差异和质量审计:

moon run cmd/main -- format .editorconfig moon run cmd/main -- format .editorconfig --write moon run cmd/main -- diff .editorconfig moon run cmd/main -- audit .editorconfig moon run cmd/main -- conflicts .editorconfig

退出码:0 表示成功,1 表示发现阻断错误或查询未命中,2 表示参数或 文件访问错误。

#启动 Studio

PowerShell:

powershell -ExecutionPolicy Bypass -File scripts\serve.ps1

CMD:

scripts\serve.cmd

看到下面的输出后访问对应地址:

MoonEditorConfig Studio: http://127.0.0.1:8765/web/index.html

Studio 默认中文,提供实时编辑、健康评分、最终属性、来源历史、Glob 匹配过程、 诊断跳转、格式化和 JSON 报告导出。所有分析都由浏览器中的 MoonBit/JS 引擎 执行,配置内容不会上传。

端口被占用时可以先设置:

$env:MOONEDITORCONFIG_PORT = "8877" powershell -ExecutionPolicy Bypass -File scripts\serve.ps1

#库 API 示例

解析和验证:

///|
let parsed = @MoonEditorConfig.parse_document(
"/repo/.editorconfig", "root = true\n[*.mbt]\nindent_size = 2\n",
)

///|
let diagnostics = @MoonEditorConfig.validate(parsed.config)

使用内存工作区解析层级配置:

///|
let files : Array[@MoonEditorConfig.VirtualFile] = [
{
path: "/repo/.editorconfig",
content: "root = true\n[*]\ncharset = utf-8\n",
},
{ path: "/repo/src/.editorconfig", content: "[*.mbt]\nindent_size = 2\n" },
]

///|
let result = @MoonEditorConfig.resolve_workspace(files, "/repo/src/main.mbt")

#项目结构

parser.mbt 可恢复语法解析 model.mbt 公共数据模型与源码范围 glob.mbt Glob 编译与匹配 glob_analysis.mbt 复杂度、示例与重叠见证 path_utils.mbt 跨平台路径处理 resolve.mbt 单文件与层级配置解析 workspace.mbt 虚拟项目和批量解析 validation.mbt 属性模式和语义验证 quality.mbt 质量评分与维护建议 conflicts.mbt 重复赋值和重叠节分析 format.mbt 规范格式化 diff.mbt 行级 Diff 与统一 Diff fixes.mbt 文本编辑与修复计划 json.mbt 机器可读输出 report.mbt 中文终端报告 studio_api.mbt 浏览器分析快照 cmd/main/ 原生 CLI web/app/ MoonBit/JS Studio 入口 web/ 页面与样式

#开发与验证

moon check --target all --deny-warn moon test --deny-warn moon fmt --check moon info moon build --target native --release cmd/main moon build --target js --release web/app

GitHub Actions 会执行相同的检查。测试覆盖解析、无效输入、Glob、路径、层级解析、 属性验证、格式化、JSON、虚拟工作区、质量审计、Diff、修复计划和配置冲突。

#规范与兼容边界

项目以 EditorConfig Specification 0.17.2 为行为依据, 并参考 EditorConfig Core Tests 设计兼容用例。

当前版本专注于 Core 行为和诊断工具,不包含具体编辑器插件。Glob 重叠分析通过 生成具体见证路径保守报告:没有找到见证不代表两个任意 Glob 在数学上完全不相交。

第三方规范、依赖和测试来源见 THIRD_PARTY.md

#许可证

Apache-2.0,详见 LICENSE

#
ConfigConflict

pub(all) struct ConfigConflict {
kind : ConflictKind
key : String
first_pattern : String
second_pattern : String
first_value : String
second_value : String
witness_path : String?
first_line : Int
second_line : Int
message : String
} derive(Eq,
Debug
)

#
ConfigConflict::render_zh

fn ConfigConflict::render_zh(self : ConfigConflict) -> String

#
ConfigLayer

pub(all) struct ConfigLayer {
path : String
directory : String
config : EditorConfig
} derive(Eq,
Debug
)

One configuration file in a root-to-leaf resolution chain.

#
ConflictKind

pub(all) enum ConflictKind {
DuplicateAssignment
RedundantAssignment
OverlappingSections
ConflictingSections
} derive(Eq,
Debug
)

#
Diagnostic

pub(all) struct Diagnostic {
line : Int
severity : Severity
code : String
message : String
hint : String
span : SourceSpan
} derive(Eq,
Debug
)

A structured, machine-readable problem report.

#
Diagnostic::render_zh

fn Diagnostic::render_zh(self : Diagnostic) -> String

#
DiffHunk

pub(all) struct DiffHunk {
old_start : Int
old_count : Int
new_start : Int
new_count : Int
lines : Array[DiffLine]
} derive(Eq,
Debug
)

#
DiffKind

pub(all) enum DiffKind {
Unchanged
Added
Removed
} derive(Eq,
Debug
)

#
DiffLine

pub(all) struct DiffLine {
kind : DiffKind
old_line : Int?
new_line : Int?
content : String
} derive(Eq,
Debug
)

#
EditorConfig

pub(all) struct EditorConfig {
path : String
root : Bool
preamble : Array[Property]
sections : Array[Section]
source_text : String
} derive(Eq,
Debug
)

Parsed representation of one .editorconfig file.

#
Finding

pub(all) struct Finding {
code : String
severity : Severity
category : FindingCategory
title : String
detail : String
suggestion : String
span : SourceSpan
} derive(Eq,
Debug
)

#
FindingCategory

pub(all) enum FindingCategory {
Syntax
Correctness
Maintainability
Portability
Style
} derive(Eq,
Debug
)

#
FixPlan

pub(all) struct FixPlan {
edits : Array[TextEdit]
safe_count : Int
review_count : Int
safe_source : String
diff : String
} derive(Eq,
Debug
)

#
FormatOptions

pub(all) struct FormatOptions {
indent : String
separator : String
final_newline : Bool
sort_properties : Bool
} derive(Eq,
Debug
)

#
FormatOptions::default

fn FormatOptions::default() -> FormatOptions

#
GlobCase

pub(all) struct GlobCase {
path : String
matched : Bool
explanation : String
} derive(Eq,
Debug
)

#
GlobMetrics

pub(all) struct GlobMetrics {
literals : Int
separators : Int
single_wildcards : Int
recursive_wildcards : Int
character_classes : Int
alternatives : Int
estimated_states : Int
matches_directories : Bool
} derive(Eq,
Debug
)

#
GlobProgram

pub(all) struct GlobProgram {
source : String
alternatives : Array[Array[GlobToken]]
diagnostics : Array[String]
} derive(Eq,
Debug
)

#
GlobProgram::matches

fn GlobProgram::matches(self : GlobProgram, path : String) -> Bool

#
GlobToken

pub(all) enum GlobToken {
Literal(UInt16)
Separator
AnyCharacter
AnySegment
AnyPath
CharacterClass(chars~ : Array[UInt16], ranges~ : Array[(UInt16, UInt16)], negated~ : Bool)
} derive(Eq,
Debug
)

#
ParseOptions

pub(all) struct ParseOptions {
path : String
allow_colon_separator : Bool
warn_duplicate_keys : Bool
warn_unknown_preamble : Bool
} derive(Eq,
Debug
)

Options controlling parser compatibility and diagnostics.

#
ParseOptions::default

fn ParseOptions::default() -> ParseOptions

Return practical defaults suitable for editor and CLI use.

#
ParseResult

pub(all) struct ParseResult {
config : EditorConfig
diagnostics : Array[Diagnostic]
} derive(Eq,
Debug
)

Result of parsing one document. Syntax errors are collected rather than raised so callers can continue providing editor feedback.

#
Property

pub(all) struct Property {
key : String
value : String
raw_key : String
raw_value : String
source : SourceLocation
span : SourceSpan
} derive(Eq,
Debug
)

A key/value assignment together with its original spelling and location.

#
PropertyKind

pub(all) enum PropertyKind {
Choice(Array[String])
Boolean
PositiveInteger
PositiveIntegerOrTab
FreeText
} derive(Eq,
Debug
)

#
PropertySchema

pub(all) struct PropertySchema {
name : String
kind : PropertyKind
description : String
standard : Bool
} derive(Eq,
Debug
)

#
PropertyStep

pub(all) struct PropertyStep {
key : String
value : String
config_path : String
pattern : String
line : Int
action : String
} derive(Eq,
Debug
)

One assignment in the history of a resolved property.

#
QualityReport

pub(all) struct QualityReport {
score : Int
grade : String
stats : QualityStats
findings : Array[Finding]
} derive(Eq,
Debug
)

#
QualityReport::has_blockers

fn QualityReport::has_blockers(self : QualityReport) -> Bool

#
QualityReport::summary_zh

fn QualityReport::summary_zh(self : QualityReport) -> String

#
QualityStats

pub(all) struct QualityStats {
sections : Int
properties : Int
standard_properties : Int
custom_properties : Int
errors : Int
warnings : Int
information : Int
} derive(Eq,
Debug
)

#
Resolution

pub(all) struct Resolution {
target : String
properties : Array[ResolvedProperty]
sections : Array[SectionTrace]
diagnostics : Array[Diagnostic]
} derive(Eq,
Debug
)

Complete resolution output for a target file.

#
Resolution::explain_zh

fn Resolution::explain_zh(self : Resolution) -> String

#
Resolution::get

fn Resolution::get(self : Resolution, key : String) -> ResolvedProperty?

#
Resolution::to_json

fn Resolution::to_json(self : Resolution, indent? : Bool) -> String

#
ResolvedProperty

pub(all) struct ResolvedProperty {
key : String
value : String
source : SourceLocation
pattern : String
config_path : String
history : Array[PropertyStep]
} derive(Eq,
Debug
)

Final value and complete override history for a property.

#
Section

pub(all) struct Section {
pattern : String
properties : Array[Property]
source : SourceLocation
span : SourceSpan
} derive(Eq,
Debug
)

A section header and the properties governed by its glob pattern.

#
SectionTrace

pub(all) struct SectionTrace {
config_path : String
pattern : String
matched : Bool
relative_path : String
line : Int
reason : String
} derive(Eq,
Debug
)

Explanation for one section considered by the resolver.

#
Severity

pub(all) enum Severity {
Error
Warning
Information
} derive(Eq,
Debug
)

Severity of a diagnostic produced while parsing or validating a file.

#
Severity::label_zh

fn Severity::label_zh(self : Severity) -> String

#
SourceLocation

pub(all) struct SourceLocation {
file : String
line : Int
column : Int
offset : Int
} derive(Eq,
Debug
)

A source location in an EditorConfig document.

#
SourceSpan

pub(all) struct SourceSpan {
start : SourceLocation
end : SourceLocation
} derive(Eq,
Debug
)

A half-open source range used by diagnostics and editor integrations.

#
TextEdit

pub(all) struct TextEdit {
start_offset : Int
end_offset : Int
replacement : String
title : String
safe : Bool
} derive(Eq,
Debug
)

#
VirtualFile

pub(all) struct VirtualFile {
path : String
content : String
} derive(Eq,
Debug
)

An in-memory file used by browser, test, and editor integrations.

#
WorkspaceResult

pub(all) struct WorkspaceResult {
target : String
discovered_configs : Array[String]
resolution : Resolution
diagnostics : Array[Diagnostic]
} derive(Eq,
Debug
)

Result of resolving a target inside a virtual project.

#
analyze_conflicts

fn analyze_conflicts(config : EditorConfig) -> Array[ConfigConflict]

Find duplicate assignments and concrete cross-section overlap witnesses. Overlap analysis is deliberately conservative: it reports only when a generated path matches both patterns and never claims patterns are disjoint.

#
analyze_glob

fn analyze_glob(pattern : String) -> GlobMetrics

Measure a glob's structural complexity for diagnostics and UI display.

#
apply_edits

fn apply_edits(source : String, edits : Array[TextEdit]) -> String

Apply non-overlapping edits. Call validate_edits first for untrusted data.

#
audit

fn audit(config : EditorConfig, initial? : Array[Diagnostic]) -> QualityReport

Perform a static quality audit without changing the source document.

#
compile_glob

fn compile_glob(pattern : String) -> GlobProgram

#
conflict_count_by_kind

fn conflict_count_by_kind(conflicts : Array[ConfigConflict], kind : ConflictKind) -> Int

#
diff_hunks

fn diff_hunks(before : String, after : String, context? : Int) -> Array[DiffHunk]

Group changed lines into unified-diff style hunks.

#
diff_lines

fn diff_lines(before : String, after : String) -> Array[DiffLine]

Compute a deterministic line-oriented diff using a longest common subsequence table. It is intended for configuration-sized documents.

#
discover_virtual_configs

fn discover_virtual_configs(files : Array[VirtualFile], target : String) -> (Array[ConfigLayer], Array[Diagnostic])

Discover .editorconfig files in leaf-to-root order without accessing the host filesystem. Search stops after parsing a file with root = true.

#
evaluate_glob

fn evaluate_glob(pattern : String, paths : Array[String]) -> Array[GlobCase]

Evaluate a pattern against many paths while retaining explanations.

#
explain_glob

fn explain_glob(pattern : String, path : String) -> String

#
format_config

fn format_config(config : EditorConfig, options? : FormatOptions) -> String

Render a canonical representation of a parsed EditorConfig document.

#
format_diff

fn format_diff(source : String) -> String

Show exactly what canonical formatting would change.

#
glob_complexity_warning

fn glob_complexity_warning(pattern : String, threshold? : Int) -> String?

Detect patterns whose state space deserves review. The matcher is memoized, so this is a maintainability signal rather than a vulnerability verdict.

#
glob_examples

fn glob_examples(pattern : String) -> Array[String]

Generate deterministic examples that help users understand a pattern.

#
glob_matches

fn glob_matches(pattern : String, path : String) -> Bool

#
glob_overlap_witness

fn glob_overlap_witness(left : String, right : String) -> String?

Look for concrete evidence that two patterns overlap by generating examples from each side. None means no witness was found, not a formal proof.

#
glob_specificity

fn glob_specificity(pattern : String) -> Int

A stable score used only to compare how specific two matching patterns are.

#
group_targets_by_property

fn group_targets_by_property(results : Array[WorkspaceResult], property_name : String) -> Map[String, Array[String]]

List files whose effective configuration differs for a selected property.

#
has_errors

fn has_errors(diagnostics : Array[Diagnostic]) -> Bool

#
needs_formatting

fn needs_formatting(source : String, options? : FormatOptions) -> Bool

#
normalize_path

fn normalize_path(path : String) -> String

Normalize separators and remove redundant . path segments.

#
parent_path

fn parent_path(path : String) -> String?

#
parse

fn parse(source : String) -> ParseResult

Parse an in-memory EditorConfig document using compatibility defaults.

#
parse_document

fn parse_document(path : String, source : String) -> ParseResult

Parse a document while retaining its source path in diagnostics.

#
parse_with_options

fn parse_with_options(source : String, options : ParseOptions) -> ParseResult

Parse an EditorConfig document with explicit options.

#
plan_fixes

fn plan_fixes(source : String) -> FixPlan

Build conservative automatic edits and separate review-required proposals.

#
property_schema

fn property_schema(name : String) -> PropertySchema?

#
relative_path

fn relative_path(base : String, target : String) -> String?

#
resolve

fn resolve(config : EditorConfig, path : String) -> Array[ResolvedProperty]

Compatibility helper for resolving one already parsed document.

#
resolve_layers

fn resolve_layers(layers : Array[ConfigLayer], target : String) -> Resolution

#
resolve_workspace

fn resolve_workspace(files : Array[VirtualFile], target : String) -> WorkspaceResult

Resolve one target using a complete in-memory project snapshot.

#
resolve_workspace_batch

fn resolve_workspace_batch(files : Array[VirtualFile], targets : Array[String]) -> Array[WorkspaceResult]

Resolve several files efficiently against the same virtual project.

#
standard_property_schemas

fn standard_property_schemas() -> Array[PropertySchema]

#
studio_snapshot

fn studio_snapshot(source : String, target : String) -> String

Analyze an in-browser document and return a self-contained JSON snapshot.

#
summarize

fn summarize(config : EditorConfig) -> String

#
uncovered_targets

fn uncovered_targets(results : Array[WorkspaceResult]) -> Array[String]

Find project files that have no matching EditorConfig section.

#
unified_diff

fn unified_diff(before : String, after : String, old_name? : String, new_name? : String, context? : Int) -> String

Render a compact unified diff suitable for terminals and reports.

#
validate

fn validate(config : EditorConfig) -> Array[Diagnostic]

#
validate_edits

fn validate_edits(edits : Array[TextEdit], source_length : Int) -> Array[String]

Validate that edits can be applied together without ambiguity.