Skip to content

Commit c859a24

Browse files
Macros: track xcconfig file end line/column numbers during parsing/loading
1 parent 5646c0f commit c859a24

File tree

3 files changed

+73
-3
lines changed

3 files changed

+73
-3
lines changed

Sources/SWBCore/MacroConfigFileLoader.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ import Foundation
3030

3131
/// Whether we failed to read the file at all. If this is `true`, the macro table is guaranteed to be empty.
3232
@_spi(Testing) public let isFileReadFailure: Bool
33+
34+
/// The final line number after parsing completes.
35+
let finalLineNumber: Int
36+
37+
/// The final column number after parsing completes.
38+
let finalColumnNumber: Int
3339
}
3440

3541
@_spi(Testing) public enum MacroConfigLoadContext: Sendable {
@@ -54,7 +60,7 @@ final class MacroConfigFileLoader: Sendable {
5460
guard let data = try? fs.read(path) else {
5561
let table = MacroValueAssignmentTable(namespace: namespace)
5662
let dependencyPaths = [path]
57-
return MacroConfigInfo(table: table, diagnostics: [], dependencyPaths: dependencyPaths, signature: filesSignature(dependencyPaths), isFileReadFailure: true)
63+
return MacroConfigInfo(table: table, diagnostics: [], dependencyPaths: dependencyPaths, signature: filesSignature(dependencyPaths), isFileReadFailure: true, finalLineNumber: 1, finalColumnNumber: 1)
5864
}
5965

6066
return loadSettingsFromConfig(data: data, path: path, namespace: namespace, searchPaths: searchPaths, filesSignature: filesSignature)
@@ -287,7 +293,7 @@ final class MacroConfigFileLoader: Sendable {
287293
let delegate = SettingsConfigFileParserDelegate(fs: fs, basePath: path?.dirname, searchPaths: searchPaths, table: tableRef, diagnostics: diagnostics, nestedConfigurations: nestedConfigs, ancestorIncludes: ancestorIncludes, developerPath: core.developerPath)
288294
let parser = MacroConfigFileParser(byteString: data, path: path ?? Path("no path to xcconfig file provided"), delegate: delegate)
289295
parser.parse()
290-
return MacroConfigInfo(table: tableRef.table, diagnostics: diagnostics.diagnostics, dependencyPaths: nestedConfigs.paths, signature: filesSignature(nestedConfigs.paths), isFileReadFailure: false)
296+
return MacroConfigInfo(table: tableRef.table, diagnostics: diagnostics.diagnostics, dependencyPaths: nestedConfigs.paths, signature: filesSignature(nestedConfigs.paths), isFileReadFailure: false, finalLineNumber: parser.finalLineNumber, finalColumnNumber: parser.finalColumnNumber)
291297
}
292298
}
293299

Sources/SWBMacro/MacroConfigFileParser.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ public final class MacroConfigFileParser {
4242
/// Current line number. Starts at one.
4343
var currLine: Int
4444

45+
/// The final line number after parsing completes.
46+
public private(set) var finalLineNumber: Int = 1
47+
48+
/// The final column number after parsing completes.
49+
public private(set) var finalColumnNumber: Int = 1
50+
51+
/// Index of the start of the current line in the byte array.
52+
private var currentLineStartIdx: Int = 0
53+
4554

4655
/// Initializes the macro expression parser with the given string and delegate. How the string is parsed depends on the particular parse method that’s invoked, such as `parseAsString()` or `parseAsStringList()`, and not on the configuration of the parser.
4756
public init(byteString: ByteString, path: Path, delegate: (any MacroConfigFileParserDelegate)?) {
@@ -55,6 +64,9 @@ public final class MacroConfigFileParser {
5564
/// Returns the current line number of the parser. This is commonly used from the custom implementations of the parser delegate function callbacks. Line numbers are one-based, and refer only to the source text of the parser itself (not taking into account any source text included using #include directives).
5665
public var lineNumber: Int { return currLine }
5766

67+
/// Returns the current column number of the parser. Column numbers are one-based.
68+
public var columnNumber: Int { return currIdx - currentLineStartIdx + 1 }
69+
5870
/*
5971

6072
Grammar:
@@ -131,6 +143,7 @@ public final class MacroConfigFileParser {
131143
}
132144
advance(advancement)
133145
currLine += 1
146+
currentLineStartIdx = currIdx
134147
}
135148

136149

@@ -461,6 +474,10 @@ public final class MacroConfigFileParser {
461474

462475
// At this point, we expect to have seen all of the input string.
463476
assert(isAtEndOfStream)
477+
478+
// Set the final line and column numbers
479+
finalLineNumber = currLine
480+
finalColumnNumber = columnNumber
464481
}
465482

466483

Tests/SWBMacroTests/MacroParsingTests.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,45 @@ fileprivate let testFileData = [
824824
expectedIncludeDirectivesCount: 1
825825
)
826826
}
827+
828+
@Test
829+
func finalLineAndColumnTracking() {
830+
// Test empty string
831+
TestMacroConfigFileParser("",
832+
expectedAssignments: [],
833+
expectedDiagnostics: [],
834+
expectedIncludeDirectivesCount: 0,
835+
expectedEndLine: 1,
836+
expectedEndColumn: 1
837+
)
838+
839+
// Test single line assignment
840+
TestMacroConfigFileParser("A = B",
841+
expectedAssignments: [(macro: "A", conditions: [], value: "B")],
842+
expectedDiagnostics: [],
843+
expectedIncludeDirectivesCount: 0,
844+
expectedEndLine: 1,
845+
expectedEndColumn: 6
846+
)
847+
848+
// Test multiple lines with empty line at end
849+
TestMacroConfigFileParser("A = B\n\n",
850+
expectedAssignments: [(macro: "A", conditions: [], value: "B")],
851+
expectedDiagnostics: [],
852+
expectedIncludeDirectivesCount: 0,
853+
expectedEndLine: 3,
854+
expectedEndColumn: 1
855+
)
856+
857+
// Test with comment-only line at end
858+
TestMacroConfigFileParser("A = B\n// This is a comment",
859+
expectedAssignments: [(macro: "A", conditions: [], value: "B")],
860+
expectedDiagnostics: [],
861+
expectedIncludeDirectivesCount: 0,
862+
expectedEndLine: 2,
863+
expectedEndColumn: 21
864+
)
865+
}
827866
}
828867

829868
// We used typealiased tuples for simplicity and readability.
@@ -832,7 +871,7 @@ typealias AssignmentInfo = (macro: String, conditions: [ConditionInfo], value: S
832871
typealias DiagnosticInfo = (level: MacroConfigFileDiagnostic.Level, kind: MacroConfigFileDiagnostic.Kind, line: Int)
833872
typealias LocationInfo = (macro: String, path: Path, startLine: Int, endLine: Int, startColumn: Int, endColumn: Int)
834873

835-
private func TestMacroConfigFileParser(_ string: String, expectedAssignments: [AssignmentInfo], expectedDiagnostics: [DiagnosticInfo], expectedLocations: [LocationInfo]? = nil, expectedIncludeDirectivesCount: Int, sourceLocation: SourceLocation = #_sourceLocation) {
874+
private func TestMacroConfigFileParser(_ string: String, expectedAssignments: [AssignmentInfo], expectedDiagnostics: [DiagnosticInfo], expectedLocations: [LocationInfo]? = nil, expectedIncludeDirectivesCount: Int, expectedEndLine: Int? = nil, expectedEndColumn: Int? = nil, sourceLocation: SourceLocation = #_sourceLocation) {
836875

837876
/// We use a custom delegate to test that we’re getting the expected results, which for the sake of convenience are just kept in (name, conds:[(cond-param, cond-value)], value) tuples, i.e. conditions is an array of two-element tuples.
838877
class ConfigFileParserTestDelegate : MacroConfigFileParserDelegate {
@@ -873,6 +912,14 @@ private func TestMacroConfigFileParser(_ string: String, expectedAssignments: [A
873912
// Create a parser, and do the parse.
874913
let parser = MacroConfigFileParser(byteString: ByteString(encodingAsUTF8: string), path: Path("TestMacroConfigFileParser().xcconfig"), delegate: delegate)
875914
parser.parse()
915+
916+
// Check the final line and column numbers if expected values are provided.
917+
if let expectedEndLine {
918+
#expect(parser.finalLineNumber == expectedEndLine, "expected final line number \(expectedEndLine), but instead got \(parser.finalLineNumber)", sourceLocation: sourceLocation)
919+
}
920+
if let expectedEndColumn {
921+
#expect(parser.finalColumnNumber == expectedEndColumn, "expected final column number \(expectedEndColumn), but instead got \(parser.finalColumnNumber)", sourceLocation: sourceLocation)
922+
}
876923

877924
// Check the assignments that the delegate saw against the expected ones.
878925
#expect(delegate.assignments == expectedAssignments, "expected assignments \(expectedAssignments), but instead got \(delegate.assignments)", sourceLocation: sourceLocation)

0 commit comments

Comments
 (0)