Crucible: Fix Formatting Churn (#5)
RC#1 — build wrote 2-space instead of .editorconfig's 4-space
- editorconfig_format.go: the glob translator escaped {/} as literals, so [*.{json,jsonc}]→indent_size=4 never matched any file and formatting fell back to [*]→2. Implemented real editorconfig brace expansion — {a,b,c} alternation and {n..m} numeric ranges. Nothing is hardcoded; saveLockfile already read .editorconfig, it just got the wrong section.
RC#2 — validate wrote a lockfile (it must be read-only)
- The three registry collectors persisted lockfiles as a side effect of collection; ValidateProject calls collection outside its snapshot guard, so the write leaked. Threaded a persistLocks bool through collectGeneratedRegistryDatasets and the damagetypes/itemprops/racialtypes collectors. Only buildNativeUnchecked (the real build allocator) passes true; validate, discovery, packaging queries, and wiki discovery pass false.
Tests added: editorconfig_glob_test.go (brace match + 4-space via brace glob), registry_readonly_test.go (read-only writes nothing; persist writes allocations).
Reviewed-on: #5
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #5.
This commit is contained in:
@@ -203,8 +203,23 @@ func editorConfigPatternMatches(pattern, relativePath string) (bool, error) {
|
||||
}
|
||||
|
||||
func editorConfigGlobRegexp(pattern string) (string, error) {
|
||||
body, err := translateEditorConfigGlobSegment(pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
expression := "^" + body + "$"
|
||||
if _, err := regexp.Compile(expression); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return expression, nil
|
||||
}
|
||||
|
||||
// translateEditorConfigGlobSegment converts an editorconfig glob into a regular
|
||||
// expression body. In addition to *, **, and ?, it implements brace expansion:
|
||||
// comma lists ({a,b,c}) become alternations and numeric ranges ({n..m}) expand
|
||||
// to the integers in the range, matching the editorconfig specification.
|
||||
func translateEditorConfigGlobSegment(pattern string) (string, error) {
|
||||
var builder strings.Builder
|
||||
builder.WriteByte('^')
|
||||
for index := 0; index < len(pattern); {
|
||||
char := pattern[index]
|
||||
switch char {
|
||||
@@ -220,22 +235,132 @@ func editorConfigGlobRegexp(pattern string) (string, error) {
|
||||
continue
|
||||
}
|
||||
builder.WriteString("[^/]*")
|
||||
index++
|
||||
case '?':
|
||||
builder.WriteString("[^/]")
|
||||
case '.', '+', '(', ')', '|', '^', '$', '{', '}', '[', ']', '\\':
|
||||
index++
|
||||
case '{':
|
||||
end := matchingEditorConfigBrace(pattern, index)
|
||||
if end < 0 {
|
||||
// An unbalanced brace is matched literally.
|
||||
builder.WriteString("\\{")
|
||||
index++
|
||||
continue
|
||||
}
|
||||
expansion, err := expandEditorConfigBrace(pattern[index+1 : end])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
builder.WriteString(expansion)
|
||||
index = end + 1
|
||||
case '.', '+', '(', ')', '|', '^', '$', '}', '[', ']', '\\':
|
||||
builder.WriteByte('\\')
|
||||
builder.WriteByte(char)
|
||||
index++
|
||||
default:
|
||||
builder.WriteByte(char)
|
||||
index++
|
||||
}
|
||||
index++
|
||||
}
|
||||
builder.WriteByte('$')
|
||||
expression := builder.String()
|
||||
if _, err := regexp.Compile(expression); err != nil {
|
||||
return "", err
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// matchingEditorConfigBrace returns the index of the '}' that closes the '{' at
|
||||
// open, accounting for nesting, or -1 when the braces are unbalanced.
|
||||
func matchingEditorConfigBrace(pattern string, open int) int {
|
||||
depth := 0
|
||||
for index := open; index < len(pattern); index++ {
|
||||
switch pattern[index] {
|
||||
case '{':
|
||||
depth++
|
||||
case '}':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return index
|
||||
}
|
||||
}
|
||||
}
|
||||
return expression, nil
|
||||
return -1
|
||||
}
|
||||
|
||||
// expandEditorConfigBrace turns the body of a brace expression into a regex
|
||||
// fragment. A comma list becomes an alternation; a lone "n..m" body becomes a
|
||||
// numeric range. A body with neither comma nor range is treated as literal
|
||||
// braces, matching the editorconfig reference implementation.
|
||||
func expandEditorConfigBrace(inner string) (string, error) {
|
||||
options := splitTopLevelBraceCommas(inner)
|
||||
if len(options) == 1 {
|
||||
if rangeExpr, ok, err := editorConfigNumericRange(inner); err != nil {
|
||||
return "", err
|
||||
} else if ok {
|
||||
return rangeExpr, nil
|
||||
}
|
||||
segment, err := translateEditorConfigGlobSegment(inner)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "\\{" + segment + "\\}", nil
|
||||
}
|
||||
parts := make([]string, 0, len(options))
|
||||
for _, option := range options {
|
||||
segment, err := translateEditorConfigGlobSegment(option)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts = append(parts, segment)
|
||||
}
|
||||
return "(?:" + strings.Join(parts, "|") + ")", nil
|
||||
}
|
||||
|
||||
// splitTopLevelBraceCommas splits a brace body on commas that are not nested
|
||||
// inside an inner brace group.
|
||||
func splitTopLevelBraceCommas(inner string) []string {
|
||||
parts := []string{}
|
||||
depth := 0
|
||||
start := 0
|
||||
for index := 0; index < len(inner); index++ {
|
||||
switch inner[index] {
|
||||
case '{':
|
||||
depth++
|
||||
case '}':
|
||||
depth--
|
||||
case ',':
|
||||
if depth == 0 {
|
||||
parts = append(parts, inner[start:index])
|
||||
start = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(parts, inner[start:])
|
||||
}
|
||||
|
||||
// editorConfigNumericRange expands "n..m" into an alternation of the integers
|
||||
// in [n, m]. It returns ok=false when the body is not a numeric range.
|
||||
func editorConfigNumericRange(inner string) (string, bool, error) {
|
||||
separator := strings.Index(inner, "..")
|
||||
if separator < 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
low, err := strconv.Atoi(strings.TrimSpace(inner[:separator]))
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
high, err := strconv.Atoi(strings.TrimSpace(inner[separator+2:]))
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
if low > high {
|
||||
low, high = high, low
|
||||
}
|
||||
const maxRangeSize = 65536
|
||||
if high-low >= maxRangeSize {
|
||||
return "", false, fmt.Errorf("editorconfig numeric range {%s} is too large", inner)
|
||||
}
|
||||
parts := make([]string, 0, high-low+1)
|
||||
for value := low; value <= high; value++ {
|
||||
parts = append(parts, strconv.Itoa(value))
|
||||
}
|
||||
return "(?:" + strings.Join(parts, "|") + ")", true, nil
|
||||
}
|
||||
|
||||
func pathBase(path string) string {
|
||||
|
||||
Reference in New Issue
Block a user