From 71dc154b9e94626a9c06d28ab0635dd048971399 Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Wed, 5 Nov 2025 14:39:37 -0800 Subject: [PATCH 1/2] Initial support for SARIF serialization of diagnostics --- include/swift/AST/DiagnosticsFrontend.def | 5 +- include/swift/Bridging/ASTGen.h | 3 + .../swift/Frontend/SARIFDiagnosticConsumer.h | 45 +++ lib/ASTGen/Sources/ASTGen/CMakeLists.txt | 2 + .../Sources/ASTGen/DiagnosticsBridge.swift | 63 ++++ .../Sources/ASTGen/SARIFConversion.swift | 243 +++++++++++++++ lib/ASTGen/Sources/ASTGen/SARIFSchema.swift | 288 ++++++++++++++++++ lib/Frontend/CMakeLists.txt | 1 + lib/Frontend/DiagnosticHelper.cpp | 10 + lib/Frontend/SARIFDiagnosticConsumer.cpp | 212 +++++++++++++ test/sarif/Outputs/artifact-index.sarif | 234 ++++++++++++++ test/sarif/Outputs/basic-error.sarif | 96 ++++++ test/sarif/Outputs/fixit.sarif | 144 +++++++++ test/sarif/Outputs/location.sarif | 69 +++++ test/sarif/Outputs/multiple-errors.sarif | 117 +++++++ test/sarif/Outputs/no-diagnostics.sarif | 22 ++ test/sarif/Outputs/notes.sarif | 48 +++ .../Outputs/sarif-json-extension.sarif.json | 96 ++++++ test/sarif/Outputs/schema-compliance.sarif | 96 ++++++ test/sarif/Outputs/severity-mapping.sarif | 144 +++++++++ test/sarif/Outputs/warning.sarif | 75 +++++ test/sarif/artifact-index.swift | 14 + test/sarif/basic-error.swift | 8 + test/sarif/fixit.swift | 19 ++ test/sarif/location.swift | 10 + test/sarif/multiple-errors.swift | 14 + test/sarif/no-diagnostics.swift | 15 + test/sarif/notes.swift | 15 + test/sarif/sarif-json-extension.swift | 8 + test/sarif/schema-compliance.swift | 8 + test/sarif/severity-mapping.swift | 12 + test/sarif/validate-sarif.py | 47 +++ test/sarif/warning.swift | 9 + 33 files changed, 2191 insertions(+), 1 deletion(-) create mode 100644 include/swift/Frontend/SARIFDiagnosticConsumer.h create mode 100644 lib/ASTGen/Sources/ASTGen/SARIFConversion.swift create mode 100644 lib/ASTGen/Sources/ASTGen/SARIFSchema.swift create mode 100644 lib/Frontend/SARIFDiagnosticConsumer.cpp create mode 100644 test/sarif/Outputs/artifact-index.sarif create mode 100644 test/sarif/Outputs/basic-error.sarif create mode 100644 test/sarif/Outputs/fixit.sarif create mode 100644 test/sarif/Outputs/location.sarif create mode 100644 test/sarif/Outputs/multiple-errors.sarif create mode 100644 test/sarif/Outputs/no-diagnostics.sarif create mode 100644 test/sarif/Outputs/notes.sarif create mode 100644 test/sarif/Outputs/sarif-json-extension.sarif.json create mode 100644 test/sarif/Outputs/schema-compliance.sarif create mode 100644 test/sarif/Outputs/severity-mapping.sarif create mode 100644 test/sarif/Outputs/warning.sarif create mode 100644 test/sarif/artifact-index.swift create mode 100644 test/sarif/basic-error.swift create mode 100644 test/sarif/fixit.swift create mode 100644 test/sarif/location.swift create mode 100644 test/sarif/multiple-errors.swift create mode 100644 test/sarif/no-diagnostics.swift create mode 100644 test/sarif/notes.swift create mode 100644 test/sarif/sarif-json-extension.swift create mode 100644 test/sarif/schema-compliance.swift create mode 100644 test/sarif/severity-mapping.swift create mode 100755 test/sarif/validate-sarif.py create mode 100644 test/sarif/warning.swift diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index fde382b745ff1..2276c0864ca50 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -101,9 +101,12 @@ ERROR(error_option_missing_required_argument, none, ERROR(cannot_open_file,none, "cannot open file '%0' (%1)", (StringRef, StringRef)) -ERROR(cannot_open_serialized_file,none, +ERROR(cannot_open_serialized_file, none, "cannot open file '%0' for diagnostics emission (%1)", (StringRef, StringRef)) +ERROR(cannot_serialize_diagnostics_to_sarif, none, + "cannot serialize diagnostics in SARIF format to file '%0': %1", + (StringRef, StringRef)) ERROR(error_open_input_file,none, "error opening input file '%0' (%1)", (StringRef, StringRef)) ERROR(error_clang_importer_create_fail,none, diff --git a/include/swift/Bridging/ASTGen.h b/include/swift/Bridging/ASTGen.h index 857a82edf42e7..58cddd9227999 100644 --- a/include/swift/Bridging/ASTGen.h +++ b/include/swift/Bridging/ASTGen.h @@ -46,6 +46,9 @@ void swift_ASTGen_renderSingleDiagnostic( void swift_ASTGen_renderQueuedDiagnostics( void *_Nonnull queued, ssize_t contextSize, ssize_t colorize, BridgedStringRef *_Nonnull renderedString); +void swift_ASTGen_renderQueuedDiagnosticsAsSARIF( + void *_Nonnull queuedDiagnosticsPtr, BridgedStringRef compilerVersion, + BridgedStringRef ouptutPath, BridgedStringRef *_Nullable errorMessage); void *_Nonnull swift_ASTGen_createPerFrontendDiagnosticState(); void swift_ASTGen_destroyPerFrontendDiagnosticState(void * _Nonnull state); diff --git a/include/swift/Frontend/SARIFDiagnosticConsumer.h b/include/swift/Frontend/SARIFDiagnosticConsumer.h new file mode 100644 index 0000000000000..4c77ac52da2f2 --- /dev/null +++ b/include/swift/Frontend/SARIFDiagnosticConsumer.h @@ -0,0 +1,45 @@ +//===--- SARIFDiagnosticConsumer.h - Export Diagnostics as SARIF -*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file defines the SARIFDiagnosticConsumer class, which exports +// diagnostics to SARIF v2.1.0 JSON format. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SARIFDIAGNOSTICCONSUMER_H +#define SWIFT_SARIFDIAGNOSTICCONSUMER_H + +#if SWIFT_BUILD_SWIFT_SYNTAX + +#include + +namespace llvm { +class StringRef; +} + +namespace swift { + +class DiagnosticConsumer; + +namespace sarif_diagnostics { +/// Create a DiagnosticConsumer that exports diagnostics to SARIF format. +/// +/// \param outputPath the file path to write the SARIF JSON to. +/// +/// \returns A new diagnostic consumer that exports diagnostics to SARIF. +std::unique_ptr createConsumer(llvm::StringRef outputPath); +} // namespace sarif_diagnostics +} // namespace swift + +#endif // SWIFT_BUILD_SWIFT_SYNTAX + +#endif diff --git a/lib/ASTGen/Sources/ASTGen/CMakeLists.txt b/lib/ASTGen/Sources/ASTGen/CMakeLists.txt index 678a23509cc43..9791dd4864830 100644 --- a/lib/ASTGen/Sources/ASTGen/CMakeLists.txt +++ b/lib/ASTGen/Sources/ASTGen/CMakeLists.txt @@ -8,6 +8,8 @@ add_pure_swift_host_library(swiftASTGen STATIC CXX_INTEROP DeclAttrs.swift Decls.swift Diagnostics.swift + SARIFSchema.swift + SARIFConversion.swift DiagnosticsBridge.swift EmbeddedSupport.swift Exprs.swift diff --git a/lib/ASTGen/Sources/ASTGen/DiagnosticsBridge.swift b/lib/ASTGen/Sources/ASTGen/DiagnosticsBridge.swift index 45e39aa634b8e..db993b17f296f 100644 --- a/lib/ASTGen/Sources/ASTGen/DiagnosticsBridge.swift +++ b/lib/ASTGen/Sources/ASTGen/DiagnosticsBridge.swift @@ -12,6 +12,7 @@ import ASTBridging import BasicBridging +import Foundation import SwiftDiagnostics import SwiftSyntax @@ -139,6 +140,9 @@ struct QueuedDiagnostics { /// The known source files var sourceFiles: [ExportedSourceFile] = [] + + /// Diagnostics grouped by file name for SARIF export + var diagnosticsMap: [String: DiagnosticInfo] = [:] } /// Create a grouped diagnostics structure in which we can add osou @@ -417,7 +421,36 @@ public func addQueuedDiagnostic( fixIts: fixIts ) + // Add to grouped diagnostics for console rendering queuedDiagnostics.pointee.grouped.addDiagnostic(diagnostic) + + updateMap( + queuedDiagnostics: queuedDiagnostics, + sourceFile: sourceFile, + diagnostic: diagnostic + ) +} + +fileprivate func updateMap( + queuedDiagnostics: UnsafeMutablePointer, + sourceFile: ExportedSourceFile, + diagnostic: Diagnostic +) { + let filePath = sourceFile.fileName + + // Only process if we have a SourceFileSyntax + guard let tree = sourceFile.syntax.as(SourceFileSyntax.self) else { + return + } + + queuedDiagnostics.pointee.diagnosticsMap[ + filePath, + default: DiagnosticInfo( + filePath: filePath, + tree: tree, + diagnostics: [] + ) + ].add(diagnostic: diagnostic) } /// Render a single diagnostic that has no source location information. @@ -561,3 +594,33 @@ public func renderCategoryFootnotes( // Clear out categories so we start fresh. state.pointee.referencedCategories = [] } + +/// Export queued diagnostics as SARIF JSON. +@_cdecl("swift_ASTGen_renderQueuedDiagnosticsAsSARIF") +public func renderQueuedDiagnosticsAsSARIF( + queuedDiagnosticsPtr: UnsafeMutableRawPointer, + bridgedCompilerVersion: BridgedStringRef, + bridgedOutputPath: BridgedStringRef, + errorMessageOutPtr: UnsafeMutablePointer +) { + + let queuedDiagnostics = queuedDiagnosticsPtr.assumingMemoryBound(to: QueuedDiagnostics.self) + + let diagnosticInfoArray = Array(queuedDiagnostics.pointee.diagnosticsMap.values) + + let toolInfo = ToolInfo( + name: "Swift Compiler", + version: String(bridged: bridgedCompilerVersion), + informationUri: "https://swift.org", + organization: "Swift Project" + ) + + let sarifLog = convertToSARIFLog(toolInfo: toolInfo, diagnosticInfoArray: diagnosticInfoArray) + + do { + try sarifLog.toJSONFile(url: URL(fileURLWithPath: String(bridged: bridgedOutputPath))) + } catch { + let errorMessage = "\(error)" + errorMessageOutPtr.pointee = allocateBridgedString(errorMessage) + } +} diff --git a/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift b/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift new file mode 100644 index 0000000000000..887e32675ca05 --- /dev/null +++ b/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift @@ -0,0 +1,243 @@ +//===----------------------------------------------------------------------===// +// +// SwiftDiagnosticsToSARIF.swift +// Converter from Swift Diagnostics to SARIF format +// +//===----------------------------------------------------------------------===// + +import Foundation + +#if compiler(>=6) +public import SwiftSyntax +public import SwiftDiagnostics +#else +import SwiftSyntax +import SwiftDiagnostics +#endif + +/// Information about the tool that produced the diagnostics. +public struct ToolInfo { + public let name: String + public let version: String + public let informationUri: String + public let organization: String + + public init( + name: String, + version: String, + informationUri: String, + organization: String + ) { + self.name = name + self.version = version + self.informationUri = informationUri + self.organization = organization + } +} + +public struct DiagnosticInfo { + public let filePath: String + let tree: SourceFileSyntax + var diagnostics: [Diagnostic] + let converter: SourceLocationConverter + + public init( + filePath: String, + tree: SourceFileSyntax, + diagnostics: [Diagnostic] + ) { + self.filePath = filePath + self.tree = tree + self.diagnostics = diagnostics + self.converter = SourceLocationConverter(fileName: filePath, tree: tree) + } + + public mutating func add(diagnostic: Diagnostic) { + diagnostics.append(diagnostic) + } + + public func getSourceLocation(position: AbsolutePosition) -> SourceLocation { + return converter.location(for: position) + } + + public func convertToSARIFResults(artifactIndex: Int) -> [Result] { + diagnostics.map { diagnostic -> Result in + convertToSARIFResult(artifactIndex: artifactIndex, diagnostic: diagnostic) + } + } + + private func convertToSARIFResult( + artifactIndex: Int, + diagnostic: Diagnostic + ) -> Result { + let ruleId = convertToSARIFRuleId(messageID: diagnostic.diagnosticID) + let message = convertToSARIFMessage(message: diagnostic.message) + let level = convertToSARIFSeverity(severity: diagnostic.diagMessage.severity) + let location = convertToSARIFLocation( + artifactIndex: artifactIndex, + sourceLocation: getSourceLocation(position: diagnostic.position), + messageText: nil + ) + + let relatedLocations = diagnostic.notes.map { note -> Location in + convertToSARIFLocation( + artifactIndex: artifactIndex, + sourceLocation: getSourceLocation(position: note.position), + messageText: note.message + ) + } + + let fixes = diagnostic.fixIts.compactMap { fixIt -> Fix? in + return convertToSARIFFix(artifactIndex: artifactIndex, fixIt: fixIt) + } + + return Result( + ruleId: ruleId, + message: message, + level: level, + kind: .fail, + locations: [location], + fixes: fixes.isEmpty ? nil : fixes, + relatedLocations: relatedLocations.isEmpty ? nil : relatedLocations + ) + } + + private func convertToSARIFRuleId(messageID: MessageID) -> String { + // MessageID contains domain and id, but they're private. + // For now, we create a string representation for the rule ID + return String(describing: messageID) + } + + private func convertToSARIFMessage(message: String) -> Message { + return Message(text: message) + } + + private func convertToSARIFSeverity(severity: DiagnosticSeverity) -> Result.Level { + switch severity { + case .error: + return .error + case .warning: + return .warning + case .note: + return .note + case .remark: + return .note + } + } + + private func convertToSARIFLocation( + artifactIndex: Int, + sourceLocation: SourceLocation, + messageText: String? + ) -> Location { + + let region = Region( + startLine: sourceLocation.line, + startColumn: sourceLocation.column + ) + + let artifactLocation = ArtifactLocation(index: artifactIndex) + + let physicalLocation = PhysicalLocation( + artifactLocation: artifactLocation, + region: region + ) + + let message = (messageText != nil) ? Message(text: messageText!) : nil + + return Location( + physicalLocation: physicalLocation, + message: message + ) + } + + private func convertToSARIFFix(artifactIndex: Int, fixIt: FixIt) -> Fix? { + let edits = fixIt.edits + guard !edits.isEmpty else { return nil } + + let replacements = edits.map { edit -> Replacement in convertToSARIFReplacement(edit: edit) } + + let artifactLocation = ArtifactLocation(index: artifactIndex) + let artifactChange = ArtifactChange(artifactLocation: artifactLocation, replacements: replacements) + + let description = Message(text: fixIt.message.message) + + return Fix(description: description, artifactChanges: [artifactChange]) + } + + private func convertToSARIFReplacement(edit: SourceEdit) -> Replacement { + let startLocation = getSourceLocation(position: edit.range.lowerBound) + let endLocation = getSourceLocation(position: edit.range.upperBound) + + let deletedRegion = Region( + startLine: startLocation.line, + startColumn: startLocation.column, + endLine: endLocation.line, + endColumn: endLocation.column + ) + + let insertedContent = edit.replacement.isEmpty ? nil : ArtifactContent(text: edit.replacement) + + return Replacement(deletedRegion: deletedRegion, insertedContent: insertedContent) + } + +} + +/// Converts diagnostics from multiple files to a single SARIF log. +/// - Parameter diagnosticsByFile: An array of tuples containing file URLs, diagnostics, and syntax trees. +/// - Returns: A SARIF log containing all converted diagnostics. +public func convertToSARIFLog(toolInfo: ToolInfo, diagnosticInfoArray: [DiagnosticInfo]) -> SARIFLog { + + let tool = convertToSARIFTool(toolInfo: toolInfo) + let (artifactArray, artifactMap) = buildSARIFArtifactCache(diagnosticInfoArray: diagnosticInfoArray) + let results = diagnosticInfoArray.flatMap { diagnosticInfo -> [Result] in + let artifactIndex = artifactMap[diagnosticInfo.filePath]! + return diagnosticInfo.convertToSARIFResults(artifactIndex: artifactIndex) + } + + let run = Run( + tool: tool, + results: results, + artifacts: artifactArray + ) + + return SARIFLog(runs: [run]) +} + +fileprivate func convertToSARIFTool(toolInfo: ToolInfo) -> Tool { + return Tool( + driver: ToolComponent( + name: toolInfo.name, + version: toolInfo.version, + informationUri: toolInfo.informationUri, + organization: toolInfo.organization + ) + ) +} + +fileprivate func buildSARIFArtifactCache(diagnosticInfoArray: [DiagnosticInfo]) -> ([Artifact], [String: Int]) { + var artifactArray: [Artifact] = [] + var artifactMap: [String: Int] = [:] + + for diagnosticInfo in diagnosticInfoArray { + let filePath = diagnosticInfo.filePath + + // don't add redundant artifact + if artifactMap[filePath] != nil { + continue + } + + let index = artifactArray.count + artifactMap[filePath] = index + + let artifact = Artifact( + location: ArtifactLocation(uri: filePath), + mimeType: "text/x-swift", + sourceLanguage: "swift" + ) + + artifactArray.append(artifact) + } + + return (artifactArray, artifactMap) +} diff --git a/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift b/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift new file mode 100644 index 0000000000000..146b5b0267e6a --- /dev/null +++ b/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift @@ -0,0 +1,288 @@ +//===----------------------------------------------------------------------===// +// +// SARIFSchema.swift +// SARIF (Static Analysis Results Interchange Format) Version 2.1.0 +// +// This file contains Swift structs representing the SARIF v2.1.0 format +// as defined by the OASIS standard. +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// The top-level object for a SARIF log file. +public struct SARIFLog: Codable, Sendable { + public let version: String = "2.1.0" + public let schema: String = "https://json.schemastore.org/sarif-2.1.0.json" + public let runs: [Run] + + enum CodingKeys: String, CodingKey { + case version + case schema = "$schema" + case runs + } + + public init( + runs: [Run] + ) { + self.runs = runs + } + + public func toJSONData( + with outputFormatting: JSONEncoder.OutputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] + ) throws -> Data { + let encoder = JSONEncoder() + encoder.outputFormatting = outputFormatting + let jsonData = try encoder.encode(self) + return jsonData + } + + public func toJSONString() throws -> String { + let jsonData = try toJSONData() + // JSONEncoder produces valid UTF-8 encoded data by default + return String(data: jsonData, encoding: .utf8)! + } + + public func toJSONFile(url: URL, options: Data.WritingOptions = []) throws { + let jsonData = try toJSONData() + try jsonData.write(to: url, options: options) + } +} + +/// Describes a single run of an analysis tool and contains the output of that run. +public struct Run: Codable, Sendable { + public let tool: Tool + public let results: [Result] + public let artifacts: [Artifact] + + public init( + tool: Tool, + results: [Result], + artifacts: [Artifact] + ) { + self.tool = tool + self.results = results + self.artifacts = artifacts + } +} + +/// Describes the analysis tool that was run. +public struct Tool: Codable, Sendable { + public let driver: ToolComponent + + public init(driver: ToolComponent) { + self.driver = driver + } +} + +/// A component of the analysis tool, such as the driver or an extension. +public struct ToolComponent: Codable, Sendable { + public let name: String + public let version: String + public let informationUri: String + public let organization: String + + public init( + name: String, + version: String, + informationUri: String, + organization: String, + ) { + self.name = name + self.version = version + self.informationUri = informationUri + self.organization = organization + } +} + +/// A result produced by an analysis tool. +public struct Result: Codable, Sendable { + public let ruleId: String + public let message: Message + public let level: Level + public let kind: Kind + public let locations: [Location] + public let fixes: [Fix]? + public let relatedLocations: [Location]? + + public init( + ruleId: String, + message: Message, + level: Level, + kind: Kind, + locations: [Location], + fixes: [Fix]? = nil, + relatedLocations: [Location]? = nil + ) { + self.ruleId = ruleId + self.message = message + self.level = level + self.kind = kind + self.locations = locations + self.fixes = fixes + self.relatedLocations = relatedLocations + } + + /// The level of a result. + public enum Level: String, Codable, Sendable { + case none + case note + case warning + case error + } + + /// The kind of result. + public enum Kind: String, Codable, Sendable { + case notApplicable + case pass + case fail + case review + case `open` + case informational + } +} + +/// A location within a programming artifact. +public struct Location: Codable, Sendable { + public let physicalLocation: PhysicalLocation + public let message: Message? + + public init( + physicalLocation: PhysicalLocation, + message: Message? = nil + ) { + self.physicalLocation = physicalLocation + self.message = message + } +} + +/// A physical location relevant to a result. +public struct PhysicalLocation: Codable, Sendable { + public let artifactLocation: ArtifactLocation + public let region: Region + + public init( + artifactLocation: ArtifactLocation, + region: Region, + ) { + self.artifactLocation = artifactLocation + self.region = region + } +} + +/// Specifies the location of an artifact. +public struct ArtifactLocation: Codable, Sendable { + public let uri: String? + public let index: Int? + + public init(uri: String? = nil, index: Int? = nil) { + self.uri = uri + self.index = index + } +} + +/// A region within an artifact where a result was detected. +public struct Region: Codable, Sendable { + public let startLine: Int + public let startColumn: Int + public let endLine: Int? + public let endColumn: Int? + public let snippet: ArtifactContent? + + public init( + startLine: Int, + startColumn: Int, + endLine: Int? = nil, + endColumn: Int? = nil, + snippet: ArtifactContent? = nil + ) { + self.startLine = startLine + self.startColumn = startColumn + self.endLine = endLine + self.endColumn = endColumn + self.snippet = snippet + } +} + +/// Encapsulates a message intended to be read by the end user. +public struct Message: Codable, Sendable { + public let text: String + public let id: String? + public let arguments: [String]? + + public init( + text: String, + id: String? = nil, + arguments: [String]? = nil + ) { + self.text = text + self.id = id + self.arguments = arguments + } +} + +/// A single artifact. +public struct Artifact: Codable, Sendable { + public let location: ArtifactLocation + public let mimeType: String + public let sourceLanguage: String + public let length: Int? + public let contents: ArtifactContent? + + public init( + location: ArtifactLocation, + mimeType: String, + sourceLanguage: String, + length: Int? = nil, + contents: ArtifactContent? = nil + ) { + self.location = location + self.mimeType = mimeType + self.sourceLanguage = sourceLanguage + self.length = length + self.contents = contents + + } +} + +/// Represents the contents of an artifact. +public struct ArtifactContent: Codable, Sendable { + public let text: String? + + public init(text: String) { + self.text = text + } +} + +/// A proposed fix for a problem indicated by a result. +public struct Fix: Codable, Sendable { + public let description: Message + public let artifactChanges: [ArtifactChange] + + public init(description: Message, artifactChanges: [ArtifactChange]) { + self.description = description + self.artifactChanges = artifactChanges + } +} + +/// A change to a single artifact. +public struct ArtifactChange: Codable, Sendable { + public let artifactLocation: ArtifactLocation + public let replacements: [Replacement] + + public init(artifactLocation: ArtifactLocation, replacements: [Replacement]) { + self.artifactLocation = artifactLocation + self.replacements = replacements + } +} + +/// The replacement of a single region of an artifact. +public struct Replacement: Codable, Sendable { + public let deletedRegion: Region + public let insertedContent: ArtifactContent? + + public init(deletedRegion: Region, insertedContent: ArtifactContent? = nil) { + self.deletedRegion = deletedRegion + self.insertedContent = insertedContent + } +} diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index 63ec151388e5f..d9a9175121091 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -20,6 +20,7 @@ add_swift_host_library(swiftFrontend STATIC ModuleInterfaceLoader.cpp ModuleInterfaceSupport.cpp PrintingDiagnosticConsumer.cpp + SARIFDiagnosticConsumer.cpp Serialization.cpp SerializedDiagnosticConsumer.cpp) add_dependencies(swiftFrontend diff --git a/lib/Frontend/DiagnosticHelper.cpp b/lib/Frontend/DiagnosticHelper.cpp index c4370f71ba90d..b32b2da0f7b82 100644 --- a/lib/Frontend/DiagnosticHelper.cpp +++ b/lib/Frontend/DiagnosticHelper.cpp @@ -23,6 +23,7 @@ #include "swift/Frontend/AccumulatingDiagnosticConsumer.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" +#include "swift/Frontend/SARIFDiagnosticConsumer.h" #include "swift/Frontend/SerializedDiagnosticConsumer.h" #include "swift/Migrator/FixitFilter.h" #include "llvm/Support/raw_ostream.h" @@ -190,6 +191,15 @@ createSerializedDiagnosticConsumerIfNeeded( auto serializedDiagnosticsPath = input.getSerializedDiagnosticsPath(); if (serializedDiagnosticsPath.empty()) return nullptr; + +#if SWIFT_BUILD_SWIFT_SYNTAX + // Check if SARIF format is requested + llvm::StringRef path(serializedDiagnosticsPath); + if (path.ends_with(".sarif") || path.ends_with(".sarif.json")) { + return sarif_diagnostics::createConsumer(serializedDiagnosticsPath); + } +#endif + return serialized_diagnostics::createConsumer(serializedDiagnosticsPath, emitMacroExpansionFiles); }); diff --git a/lib/Frontend/SARIFDiagnosticConsumer.cpp b/lib/Frontend/SARIFDiagnosticConsumer.cpp new file mode 100644 index 0000000000000..1b64e77542a9e --- /dev/null +++ b/lib/Frontend/SARIFDiagnosticConsumer.cpp @@ -0,0 +1,212 @@ +//===--- SARIFDiagnosticConsumer.cpp - Export Diagnostics as SARIF -------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements the SARIF diagnostic consumer, which exports +// diagnostics in SARIF v2.1.0 JSON format. +// +//===----------------------------------------------------------------------===// + +#if SWIFT_BUILD_SWIFT_SYNTAX + +#include "swift/AST/DiagnosticConsumer.h" +#include "swift/AST/DiagnosticsFrontend.h" +#include "swift/Basic/SourceManager.h" +#include "swift/Bridging/ASTGen.h" +#include "swift/Frontend/PrintingDiagnosticConsumer.h" +#include "llvm/Support/raw_ostream.h" + +using namespace swift; + +namespace { + +/// Diagnostic consumer that exports diagnostics to SARIF format. +class SARIFDiagnosticConsumer : public DiagnosticConsumer { + std::string OutputPath; + void *queuedDiagnostics = nullptr; + void *perFrontendState = nullptr; + llvm::DenseMap queuedBuffers; + llvm::DenseMap, void *> sourceFileSyntax; + bool CalledFinishProcessing = false; + +public: + SARIFDiagnosticConsumer(StringRef outputPath) : OutputPath(outputPath.str()) { + queuedDiagnostics = swift_ASTGen_createQueuedDiagnostics(); + perFrontendState = swift_ASTGen_createPerFrontendDiagnosticState(); + } + + ~SARIFDiagnosticConsumer() { + assert(CalledFinishProcessing && "did not call finishProcessing()"); + + if (perFrontendState) { + swift_ASTGen_destroyPerFrontendDiagnosticState(perFrontendState); + } + + // Queued diagnostics should have been destroyed in finishProcessing + assert(!queuedDiagnostics && "unflushed diagnostics"); + + for (const auto &entry : sourceFileSyntax) { + swift_ASTGen_destroySourceFile(entry.second); + } + } + + bool finishProcessing() override { + assert(!CalledFinishProcessing && + "called finishProcessing() multiple times"); + CalledFinishProcessing = true; + + if (!queuedDiagnostics) + return false; + + BridgedStringRef bridgedErrorMessage{nullptr, 0}; + swift_ASTGen_renderQueuedDiagnosticsAsSARIF( + queuedDiagnostics, + llvm::StringRef(swift::version::getCompilerVersion()), + llvm::StringRef(OutputPath), &bridgedErrorMessage); + + // Clean up queued diagnostics + swift_ASTGen_destroyQueuedDiagnostics(queuedDiagnostics); + queuedDiagnostics = nullptr; + queuedBuffers.clear(); + + const llvm::StringRef errorMessage = bridgedErrorMessage.unbridged(); + + if (errorMessage.data()) { + SourceManager dummyMgr; + DiagnosticEngine DE(dummyMgr); + PrintingDiagnosticConsumer PDC; + DE.addConsumer(PDC); + DE.diagnose(SourceLoc(), diag::cannot_serialize_diagnostics_to_sarif, + OutputPath, errorMessage); + + swift_ASTGen_freeBridgedString(bridgedErrorMessage); + + return true; + } + + return false; + } + + void handleDiagnostic(SourceManager &SM, + const DiagnosticInfo &Info) override { + if (!queuedDiagnostics) + return; + + // Queue the source buffer(s) for this diagnostic + if (Info.Loc.isValid()) { + unsigned bufferID = SM.findBufferContainingLoc(Info.Loc); + queueBuffer(SM, bufferID); + } + + // Add the diagnostic to the queue + addQueuedDiagnostic(Info, SM); + + // Add child diagnostics (notes) + for (auto *childNote : Info.ChildDiagnosticInfo) { + addQueuedDiagnostic(*childNote, SM); + } + } + +private: + void addQueuedDiagnostic(const DiagnosticInfo &info, SourceManager &SM) { + llvm::SmallString<256> text; + { + llvm::raw_svector_ostream out(text); + DiagnosticEngine::formatDiagnosticText(out, info.FormatString, + info.FormatArgs); + } + + StringRef documentationPath = info.CategoryDocumentationURL; + + SmallVector fixIts; + for (const auto &fixIt : info.FixIts) { + fixIts.push_back(BridgedFixIt{fixIt.getRange(), fixIt.getText()}); + } + + swift_ASTGen_addQueuedDiagnostic( + queuedDiagnostics, perFrontendState, text.str(), info.Kind, info.Loc, + info.Category, documentationPath, info.Ranges.data(), + info.Ranges.size(), llvm::ArrayRef(fixIts)); + } + + void *getSourceFileSyntax(SourceManager &sourceMgr, unsigned bufferID, + StringRef displayName) { + auto known = sourceFileSyntax.find({&sourceMgr, bufferID}); + if (known != sourceFileSyntax.end()) + return known->second; + + auto bufferContents = sourceMgr.getEntireTextForBuffer(bufferID); + auto sourceFile = swift_ASTGen_parseSourceFile( + bufferContents, StringRef{"module"}, displayName, + /*declContextPtr=*/nullptr, BridgedGeneratedSourceFileKindNone); + + sourceFileSyntax[{&sourceMgr, bufferID}] = sourceFile; + return sourceFile; + } + + void queueBuffer(SourceManager &sourceMgr, unsigned bufferID) { + if (queuedBuffers.count(bufferID)) + return; + + // Find the parent and position in parent, if there is one. + int parentID = -1; + int positionInParent = 0; + std::string displayName; + + auto generatedSourceInfo = sourceMgr.getGeneratedSourceInfo(bufferID); + if (generatedSourceInfo) { + SourceLoc parentLoc = generatedSourceInfo->originalSourceRange.getEnd(); + if (parentLoc.isValid()) { + parentID = sourceMgr.findBufferContainingLoc(parentLoc); + positionInParent = sourceMgr.getLocOffsetInBuffer(parentLoc, parentID); + + // Queue the parent buffer recursively + queueBuffer(sourceMgr, parentID); + } + + if (!generatedSourceInfo->macroName.empty()) { + if (generatedSourceInfo->attachedMacroCustomAttr) + displayName = "macro expansion @" + generatedSourceInfo->macroName; + else + displayName = "macro expansion #" + generatedSourceInfo->macroName; + } + } + + if (displayName.empty()) { + displayName = + sourceMgr + .getDisplayNameForLoc(sourceMgr.getLocForBufferStart(bufferID)) + .str(); + } + + auto sourceFile = getSourceFileSyntax(sourceMgr, bufferID, displayName); + swift_ASTGen_addQueuedSourceFile(queuedDiagnostics, bufferID, sourceFile, + (const uint8_t *)displayName.data(), + displayName.size(), parentID, + positionInParent); + + queuedBuffers[bufferID] = sourceFile; + } +}; + +} // anonymous namespace + +namespace swift { +namespace sarif_diagnostics { + +std::unique_ptr createConsumer(StringRef outputPath) { + return std::make_unique(outputPath); +} + +} // namespace sarif_diagnostics +} // namespace swift + +#endif // SWIFT_BUILD_SWIFT_SYNTAX diff --git a/test/sarif/Outputs/artifact-index.sarif b/test/sarif/Outputs/artifact-index.sarif new file mode 100644 index 0000000000000..26bf0ff8b3a7f --- /dev/null +++ b/test/sarif/Outputs/artifact-index.sarif @@ -0,0 +1,234 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'a' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 7, + "startColumn" : 1, + "startLine" : 7 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 7 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 11 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'b' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 10, + "startColumn" : 1, + "startLine" : 10 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 10 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 14 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'c' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 13, + "startColumn" : 1, + "startLine" : 13 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 13 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/basic-error.sarif b/test/sarif/Outputs/basic-error.sarif new file mode 100644 index 0000000000000..93f56900d3a5d --- /dev/null +++ b/test/sarif/Outputs/basic-error.sarif @@ -0,0 +1,96 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'x' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 7, + "startColumn" : 1, + "startLine" : 7 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 7 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/fixit.sarif b/test/sarif/Outputs/fixit.sarif new file mode 100644 index 0000000000000..8a4cfd3127c8a --- /dev/null +++ b/test/sarif/Outputs/fixit.sarif @@ -0,0 +1,144 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 15, + "endLine" : 18, + "startColumn" : 15, + "startLine" : 18 + }, + "insertedContent" : { + "text" : "x: <#Int#>, y: <#Int#>" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 14, + "startLine" : 18 + } + } + } + ], + "message" : { + "text" : "missing arguments for parameters 'x', 'y' in call" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 8, + "startLine" : 13 + } + } + } + ], + "message" : { + "text" : "'init(x:y:)' declared here" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 6, + "endLine" : 8, + "startColumn" : 3, + "startLine" : 8 + }, + "insertedContent" : { + "text" : "let" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "warning", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 7, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "variable 'x' was never mutated; consider changing to 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/location.sarif b/test/sarif/Outputs/location.sarif new file mode 100644 index 0000000000000..fbbabb155931d --- /dev/null +++ b/test/sarif/Outputs/location.sarif @@ -0,0 +1,69 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 13, + "startLine" : 9 + } + } + } + ], + "message" : { + "text" : "binary operator '+' cannot be applied to operands of type 'Int' and 'String'" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 13, + "startLine" : 9 + } + } + } + ], + "message" : { + "text" : "overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/multiple-errors.sarif b/test/sarif/Outputs/multiple-errors.sarif new file mode 100644 index 0000000000000..a100241a069c9 --- /dev/null +++ b/test/sarif/Outputs/multiple-errors.sarif @@ -0,0 +1,117 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'a' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 7, + "startColumn" : 1, + "startLine" : 7 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 7 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 14, + "startLine" : 10 + } + } + } + ], + "message" : { + "text" : "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/no-diagnostics.sarif b/test/sarif/Outputs/no-diagnostics.sarif new file mode 100644 index 0000000000000..1fd6396eb08dd --- /dev/null +++ b/test/sarif/Outputs/no-diagnostics.sarif @@ -0,0 +1,22 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + + ], + "results" : [ + + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/notes.sarif b/test/sarif/Outputs/notes.sarif new file mode 100644 index 0000000000000..38b3195dc34c5 --- /dev/null +++ b/test/sarif/Outputs/notes.sarif @@ -0,0 +1,48 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 7, + "startLine" : 13 + } + } + } + ], + "message" : { + "text" : "function is unused" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/sarif-json-extension.sarif.json b/test/sarif/Outputs/sarif-json-extension.sarif.json new file mode 100644 index 0000000000000..d46c111d8c6dd --- /dev/null +++ b/test/sarif/Outputs/sarif-json-extension.sarif.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "location": { + "uri": "FILE[0]" + }, + "mimeType": "text/x-swift", + "sourceLanguage": "swift" + } + ], + "results": [ + { + "kind": "fail", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 1, + "startLine": 8 + } + } + } + ], + "message": { + "text": "cannot assign to value: 'x' is a 'let' constant" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "index": 0 + }, + "replacements": [ + { + "deletedRegion": { + "endColumn": 4, + "endLine": 7, + "startColumn": 1, + "startLine": 7 + }, + "insertedContent": { + "text": "var" + } + } + ] + } + ], + "description": { + "text": "" + } + } + ], + "kind": "fail", + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 1, + "startLine": 7 + } + } + } + ], + "message": { + "text": "change 'let' to 'var' to make it mutable" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" + } + } + } + ], + "version": "2.1.0" +} diff --git a/test/sarif/Outputs/schema-compliance.sarif b/test/sarif/Outputs/schema-compliance.sarif new file mode 100644 index 0000000000000..93f56900d3a5d --- /dev/null +++ b/test/sarif/Outputs/schema-compliance.sarif @@ -0,0 +1,96 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'x' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 7, + "startColumn" : 1, + "startLine" : 7 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 7 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/severity-mapping.sarif b/test/sarif/Outputs/severity-mapping.sarif new file mode 100644 index 0000000000000..7e7e8654e9021 --- /dev/null +++ b/test/sarif/Outputs/severity-mapping.sarif @@ -0,0 +1,144 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "kind" : "fail", + "level" : "error", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "cannot assign to value: 'a' is a 'let' constant" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 4, + "endLine" : 7, + "startColumn" : 1, + "startLine" : 7 + }, + "insertedContent" : { + "text" : "var" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "note", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 1, + "startLine" : 7 + } + } + } + ], + "message" : { + "text" : "change 'let' to 'var' to make it mutable" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 8, + "endLine" : 11, + "startColumn" : 3, + "startLine" : 11 + }, + "insertedContent" : { + "text" : "_" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "warning", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 7, + "startLine" : 11 + } + } + } + ], + "message" : { + "text" : "initialization of variable 'x' was never used; consider replacing with assignment to '_' or removing it" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/Outputs/warning.sarif b/test/sarif/Outputs/warning.sarif new file mode 100644 index 0000000000000..8d8c3e834586b --- /dev/null +++ b/test/sarif/Outputs/warning.sarif @@ -0,0 +1,75 @@ +{ + "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", + "runs" : [ + { + "artifacts" : [ + { + "location" : { + "uri" : "FILE[0]" + }, + "mimeType" : "text/x-swift", + "sourceLanguage" : "swift" + } + ], + "results" : [ + { + "fixes" : [ + { + "artifactChanges" : [ + { + "artifactLocation" : { + "index" : 0 + }, + "replacements" : [ + { + "deletedRegion" : { + "endColumn" : 16, + "endLine" : 8, + "startColumn" : 3, + "startLine" : 8 + }, + "insertedContent" : { + "text" : "_" + } + } + ] + } + ], + "description" : { + "text" : "" + } + } + ], + "kind" : "fail", + "level" : "warning", + "locations" : [ + { + "physicalLocation" : { + "artifactLocation" : { + "index" : 0 + }, + "region" : { + "startColumn" : 7, + "startLine" : 8 + } + } + } + ], + "message" : { + "text" : "initialization of variable 'unusedVar' was never used; consider replacing with assignment to '_' or removing it" + }, + "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool" : { + "driver" : { + "informationUri" : "https://swift.org", + "name" : "Swift Compiler", + "organization" : "Swift Project", + "version" : "6.3" + } + } + } + ], + "version" : "2.1.0" +} diff --git a/test/sarif/artifact-index.swift b/test/sarif/artifact-index.swift new file mode 100644 index 0000000000000..7a9acb89748b5 --- /dev/null +++ b/test/sarif/artifact-index.swift @@ -0,0 +1,14 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/artifact-index.sarif %t/output.sarif %s + +// Test artifact index usage - all diagnostics should reference by index not URI + +let a = 5 +a = 10 // error 1 + +let b = 3 +b = 20 // error 2 + +let c = 7 +c = 30 // error 3 diff --git a/test/sarif/basic-error.swift b/test/sarif/basic-error.swift new file mode 100644 index 0000000000000..2f5afc2264b63 --- /dev/null +++ b/test/sarif/basic-error.swift @@ -0,0 +1,8 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/basic-error.sarif %t/output.sarif %s + +// Test basic error diagnostic output in SARIF format + +let x = 5 +x = 10 // error: cannot assign to value: 'x' is a 'let' constant diff --git a/test/sarif/fixit.swift b/test/sarif/fixit.swift new file mode 100644 index 0000000000000..d35f22d4c541a --- /dev/null +++ b/test/sarif/fixit.swift @@ -0,0 +1,19 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/fixit.sarif %t/output.sarif %s + +// Test Fix-It suggestions in SARIF format + +func testFixIt() { + var x: Int + print(x) // error: variable 'x' used before being initialized + // fix-it: initialize 'x' with a value +} + +struct Point { + var x: Int + var y: Int +} + +let p = Point() // error: missing arguments for parameters 'x', 'y' in call + // fix-it: insert ', x: <#Int#>, y: <#Int#>' diff --git a/test/sarif/location.swift b/test/sarif/location.swift new file mode 100644 index 0000000000000..76b903cb9279e --- /dev/null +++ b/test/sarif/location.swift @@ -0,0 +1,10 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/location.sarif %t/output.sarif %s + +// Test location information in SARIF format + +func testLocation() { + let x = 5 + let y = x + "string" // error at line 8, column 17 +} diff --git a/test/sarif/multiple-errors.swift b/test/sarif/multiple-errors.swift new file mode 100644 index 0000000000000..3b5e9e1e9c52e --- /dev/null +++ b/test/sarif/multiple-errors.swift @@ -0,0 +1,14 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/multiple-errors.sarif %t/output.sarif %s + +// Test multiple errors in SARIF format + +let a = 5 +a = 10 // error: cannot assign to value: 'a' is a 'let' constant + +let b: Int = "string" // error: cannot convert value of type 'String' to specified type 'Int' + +func foo() -> Int { + // error: missing return in a function expected to return 'Int' +} diff --git a/test/sarif/no-diagnostics.swift b/test/sarif/no-diagnostics.swift new file mode 100644 index 0000000000000..40db5ac9a8483 --- /dev/null +++ b/test/sarif/no-diagnostics.swift @@ -0,0 +1,15 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/no-diagnostics.sarif %t/output.sarif %s + +// Test valid code with no diagnostics - should produce empty results + +func validFunction() -> Int { + return 42 +} + +let validVariable = "Hello, World!" + +struct ValidStruct { + var property: String +} diff --git a/test/sarif/notes.swift b/test/sarif/notes.swift new file mode 100644 index 0000000000000..b77cebff42be7 --- /dev/null +++ b/test/sarif/notes.swift @@ -0,0 +1,15 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/notes.sarif %t/output.sarif %s + +// Test note diagnostics (related locations) in SARIF format + +class MyClass { + func myMethod() {} +} + +func testNotes() { + let obj = MyClass() + obj.myMethod // error: cannot reference instance method without parentheses + // note: add parentheses to call the method +} diff --git a/test/sarif/sarif-json-extension.swift b/test/sarif/sarif-json-extension.swift new file mode 100644 index 0000000000000..be2a5a45acb20 --- /dev/null +++ b/test/sarif/sarif-json-extension.swift @@ -0,0 +1,8 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif.json 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/sarif-json-extension.sarif.json %t/output.sarif.json %s + +// Test .sarif.json extension support + +let x = 5 +x = 10 diff --git a/test/sarif/schema-compliance.swift b/test/sarif/schema-compliance.swift new file mode 100644 index 0000000000000..5cba3c6e689b3 --- /dev/null +++ b/test/sarif/schema-compliance.swift @@ -0,0 +1,8 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/schema-compliance.sarif %t/output.sarif %s + +// Test SARIF schema compliance and structure validation + +let x = 5 +x = 10 // error diff --git a/test/sarif/severity-mapping.swift b/test/sarif/severity-mapping.swift new file mode 100644 index 0000000000000..3ea2185e86569 --- /dev/null +++ b/test/sarif/severity-mapping.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/severity-mapping.sarif %t/output.sarif %s + +// Test severity mapping (error, warning, note) to SARIF levels + +let a = 5 +a = 10 // error + +func testWarning() { + var x = 42 // warning: unused +} diff --git a/test/sarif/validate-sarif.py b/test/sarif/validate-sarif.py new file mode 100755 index 0000000000000..be8c4bb30ba27 --- /dev/null +++ b/test/sarif/validate-sarif.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +SARIF v2.1.0 validator for Swift compiler test output. +Validates JSON structure and required fields without exact text matching. +""" + +import json +import sys + +def readfile(path): + with open(path) as f: + return f.read() + +def process_sarif(sarif_expected_file, sarif_actual_file, swift_input_files): + sarif_expected_str = readfile(sarif_expected_file) + sarif_actual = json.loads(readfile(sarif_actual_file)) + + for (index, swift_input_file) in enumerate(swift_input_files): + pattern = f"FILE[{index}]" + sarif_expected_str = sarif_expected_str.replace(pattern, swift_input_file) + + sarif_expected = json.loads(sarif_expected_str) + + return (sarif_actual, sarif_expected) + +def main(): + if len(sys.argv) < 4: + print("Usage: validate-sarif.py sarif_expected_file sarif_actual_file swift_input_file [swift_input_file ...]", file=sys.stderr) + sys.exit(1) + + (sarif_actual, sarif_expected) = process_sarif(sys.argv[1], sys.argv[2], sys.argv[3:]) + + matching = sarif_actual == sarif_expected + + if matching: + print("SARIF validation passed") + return 0 + else: + print("SARIF validation failed:", file=sys.stderr) + print("Expected:") + print(json.dumps(sarif_expected, indent = 4)) + print("Actual:") + print(json.dumps(sarif_actual, indent = 4)) + return 1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test/sarif/warning.swift b/test/sarif/warning.swift new file mode 100644 index 0000000000000..2eeb8be15c905 --- /dev/null +++ b/test/sarif/warning.swift @@ -0,0 +1,9 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/warning.sarif %t/output.sarif %s + +// Test warning diagnostic output in SARIF format + +func testWarning() { + var unusedVar = 42 // warning: variable 'unusedVar' was never used +} From 68a6c374cfdac52e0881ada19e9a9dee6f673304 Mon Sep 17 00:00:00 2001 From: Aviral Goel Date: Thu, 6 Nov 2025 14:54:03 -0800 Subject: [PATCH 2/2] Add support for serializing logical locations --- .../Sources/ASTGen/SARIFConversion.swift | 121 +- lib/ASTGen/Sources/ASTGen/SARIFSchema.swift | 23 + test/sarif/Outputs/artifact-index.sarif | 234 ---- test/sarif/Outputs/basic-error.sarif | 96 -- test/sarif/Outputs/errors.sarif | 199 +++ test/sarif/Outputs/fixit.sarif | 144 --- test/sarif/Outputs/fixits.sarif | 171 +++ test/sarif/Outputs/location.sarif | 69 - test/sarif/Outputs/locations.sarif | 1107 +++++++++++++++++ test/sarif/Outputs/multiple-errors.sarif | 117 -- test/sarif/Outputs/notes.sarif | 61 +- .../Outputs/sarif-json-extension.sarif.json | 7 + test/sarif/Outputs/schema-compliance.sarif | 96 -- test/sarif/Outputs/severity-mapping.sarif | 144 --- test/sarif/Outputs/warning.sarif | 75 -- test/sarif/Outputs/warnings.sarif | 88 ++ test/sarif/artifact-index.swift | 14 - test/sarif/basic-error.swift | 8 - .../{multiple-errors.swift => errors.swift} | 9 +- test/sarif/{fixit.swift => fixits.swift} | 4 +- test/sarif/location.swift | 10 - test/sarif/locations.swift | 183 +++ test/sarif/notes.swift | 2 - test/sarif/schema-compliance.swift | 8 - test/sarif/severity-mapping.swift | 12 - test/sarif/validate-sarif.py | 9 +- test/sarif/{warning.swift => warnings.swift} | 4 +- 27 files changed, 1945 insertions(+), 1070 deletions(-) delete mode 100644 test/sarif/Outputs/artifact-index.sarif delete mode 100644 test/sarif/Outputs/basic-error.sarif create mode 100644 test/sarif/Outputs/errors.sarif delete mode 100644 test/sarif/Outputs/fixit.sarif create mode 100644 test/sarif/Outputs/fixits.sarif delete mode 100644 test/sarif/Outputs/location.sarif create mode 100644 test/sarif/Outputs/locations.sarif delete mode 100644 test/sarif/Outputs/multiple-errors.sarif delete mode 100644 test/sarif/Outputs/schema-compliance.sarif delete mode 100644 test/sarif/Outputs/severity-mapping.sarif delete mode 100644 test/sarif/Outputs/warning.sarif create mode 100644 test/sarif/Outputs/warnings.sarif delete mode 100644 test/sarif/artifact-index.swift delete mode 100644 test/sarif/basic-error.swift rename test/sarif/{multiple-errors.swift => errors.swift} (70%) rename test/sarif/{fixit.swift => fixits.swift} (80%) delete mode 100644 test/sarif/location.swift create mode 100644 test/sarif/locations.swift delete mode 100644 test/sarif/schema-compliance.swift delete mode 100644 test/sarif/severity-mapping.swift rename test/sarif/{warning.swift => warnings.swift} (63%) diff --git a/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift b/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift index 887e32675ca05..a5f00f50c4ff6 100644 --- a/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift +++ b/lib/ASTGen/Sources/ASTGen/SARIFConversion.swift @@ -75,14 +75,14 @@ public struct DiagnosticInfo { let level = convertToSARIFSeverity(severity: diagnostic.diagMessage.severity) let location = convertToSARIFLocation( artifactIndex: artifactIndex, - sourceLocation: getSourceLocation(position: diagnostic.position), + position: diagnostic.position, messageText: nil ) let relatedLocations = diagnostic.notes.map { note -> Location in convertToSARIFLocation( artifactIndex: artifactIndex, - sourceLocation: getSourceLocation(position: note.position), + position: note.position, messageText: note.message ) } @@ -127,10 +127,12 @@ public struct DiagnosticInfo { private func convertToSARIFLocation( artifactIndex: Int, - sourceLocation: SourceLocation, + position: AbsolutePosition, messageText: String? ) -> Location { + let sourceLocation = getSourceLocation(position: position) + let region = Region( startLine: sourceLocation.line, startColumn: sourceLocation.column @@ -145,8 +147,12 @@ public struct DiagnosticInfo { let message = (messageText != nil) ? Message(text: messageText!) : nil + // Compute logical locations (function, class, etc.) containing this position + let logicalLocations = convertToSARIFLogicalLocations(position: position) + return Location( physicalLocation: physicalLocation, + logicalLocations: logicalLocations, message: message ) } @@ -181,6 +187,115 @@ public struct DiagnosticInfo { return Replacement(deletedRegion: deletedRegion, insertedContent: insertedContent) } + private func convertToSARIFLogicalLocations(position: AbsolutePosition) -> [LogicalLocation]? { + + // Find the token at this position + guard let token = tree.token(at: position) else { + return nil + } + + var currentNode: Syntax? = Syntax(token) + var nodes: [Syntax] = [] + + // Walk up the syntax tree to find enclosing declarations + while let node = currentNode { + nodes.append(node) + currentNode = node.parent + } + + nodes.reverse() + + var parentIndex: Int? = nil + var fullyQualifiedParentName = "" + + let logicalLocations: [LogicalLocation] = nodes.flatMap { + node -> LogicalLocation? in + let maybeLocation = convertToSARIFLogicalLocation( + node: node, + parentIndex: parentIndex, + fullyQualifiedParentName: fullyQualifiedParentName + ) + if let location = maybeLocation { + parentIndex = parentIndex == nil ? 0 : (parentIndex! + 1) + fullyQualifiedParentName = location.fullyQualifiedName + } + return maybeLocation + } + + return logicalLocations.isEmpty ? nil : logicalLocations + } + + private func convertToSARIFLogicalLocation(node: Syntax, parentIndex: Int?, fullyQualifiedParentName: String) + -> LogicalLocation? + { + if let (name, kind) = getSyntaxInfo(node: node) { + let fullyQualifiedName = fullyQualifiedParentName == "" ? name : (fullyQualifiedParentName + "." + name) + return LogicalLocation( + name: name, + fullyQualifiedName: fullyQualifiedName, + kind: kind, + parentIndex: parentIndex + ) + } else { + return nil + } + } + + private func getSyntaxInfo(node: Syntax) -> (String, String)? { + if let funcDecl = node.as(FunctionDeclSyntax.self) { + return (funcDecl.name.text, "function") + } else if let initDecl = node.as(InitializerDeclSyntax.self) { + return ("init", "function") + } else if let deinitDecl = node.as(DeinitializerDeclSyntax.self) { + return ("deinit", "function") + } else if let subscriptDecl = node.as(SubscriptDeclSyntax.self) { + return ("subscript", "function") + } else if let accessorDecl = node.as(AccessorDeclSyntax.self) { + return (accessorDecl.accessorSpecifier.text, "function") + } else if let classDecl = node.as(ClassDeclSyntax.self) { + return (classDecl.name.text, "class") + } else if let structDecl = node.as(StructDeclSyntax.self) { + return (structDecl.name.text, "struct") + } else if let enumDecl = node.as(EnumDeclSyntax.self) { + return (enumDecl.name.text, "enum") + } else if let actorDecl = node.as(ActorDeclSyntax.self) { + return (actorDecl.name.text, "class") + } else if let protocolDecl = node.as(ProtocolDeclSyntax.self) { + return (protocolDecl.name.text, "protocol") + } else if let extensionDecl = node.as(ExtensionDeclSyntax.self) { + return (extensionDecl.extendedType.trimmedDescription, "extension") + } else if let typeAliasDecl = node.as(TypeAliasDeclSyntax.self) { + return (typeAliasDecl.name.text, "type") + } else if let associatedTypeDecl = node.as(AssociatedTypeDeclSyntax.self) { + return (associatedTypeDecl.name.text, "type") + } else if let macroDecl = node.as(MacroDeclSyntax.self) { + return (macroDecl.name.text, "macro") + } else if let varDecl = node.as(VariableDeclSyntax.self) { + // For variable declarations, try to get the first binding's name + if let binding = varDecl.bindings.first, + let pattern = binding.pattern.as(IdentifierPatternSyntax.self) + { + return (pattern.identifier.text, "variable") + } else { + return ("variable", "variable") + } + } else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) { + // For enum cases, try to get the first element's name + if let element = enumCaseDecl.elements.first { + return (element.name.text, "enumMember") + } else { + return ("case", "enumMember") + } + } else if let importDecl = node.as(ImportDeclSyntax.self) { + return (importDecl.path.description.trimmingCharacters(in: .whitespaces), "module") + } else if let operatorDecl = node.as(OperatorDeclSyntax.self) { + return (operatorDecl.name.text, "function") + } else if let precedenceGroupDecl = node.as(PrecedenceGroupDeclSyntax.self) { + return (precedenceGroupDecl.name.text, "type") + } else { + return nil + } + } } /// Converts diagnostics from multiple files to a single SARIF log. diff --git a/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift b/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift index 146b5b0267e6a..f1137f6d601fa 100644 --- a/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift +++ b/lib/ASTGen/Sources/ASTGen/SARIFSchema.swift @@ -145,17 +145,40 @@ public struct Result: Codable, Sendable { /// A location within a programming artifact. public struct Location: Codable, Sendable { public let physicalLocation: PhysicalLocation + public let logicalLocations: [LogicalLocation]? public let message: Message? public init( physicalLocation: PhysicalLocation, + logicalLocations: [LogicalLocation]? = nil, message: Message? = nil ) { self.physicalLocation = physicalLocation + self.logicalLocations = logicalLocations self.message = message } } +/// A logical location such as a function, class, or namespace. +public struct LogicalLocation: Codable, Sendable { + public let name: String + public let fullyQualifiedName: String + public let kind: String + public let parentIndex: Int? + + public init( + name: String, + fullyQualifiedName: String, + kind: String, + parentIndex: Int? = nil + ) { + self.name = name + self.fullyQualifiedName = fullyQualifiedName + self.kind = kind + self.parentIndex = parentIndex + } +} + /// A physical location relevant to a result. public struct PhysicalLocation: Codable, Sendable { public let artifactLocation: ArtifactLocation diff --git a/test/sarif/Outputs/artifact-index.sarif b/test/sarif/Outputs/artifact-index.sarif deleted file mode 100644 index 26bf0ff8b3a7f..0000000000000 --- a/test/sarif/Outputs/artifact-index.sarif +++ /dev/null @@ -1,234 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'a' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 7, - "startColumn" : 1, - "startLine" : 7 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 7 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 11 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'b' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 10, - "startColumn" : 1, - "startLine" : 10 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 10 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 14 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'c' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 13, - "startColumn" : 1, - "startLine" : 13 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 13 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/basic-error.sarif b/test/sarif/Outputs/basic-error.sarif deleted file mode 100644 index 93f56900d3a5d..0000000000000 --- a/test/sarif/Outputs/basic-error.sarif +++ /dev/null @@ -1,96 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'x' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 7, - "startColumn" : 1, - "startLine" : 7 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 7 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/errors.sarif b/test/sarif/Outputs/errors.sarif new file mode 100644 index 0000000000000..83fae3fd7bdf8 --- /dev/null +++ b/test/sarif/Outputs/errors.sarif @@ -0,0 +1,199 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "location": { + "uri": "FILE[0]" + }, + "mimeType": "text/x-swift", + "sourceLanguage": "swift" + } + ], + "results": [ + { + "kind": "fail", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 1, + "startLine": 6 + } + } + } + ], + "message": { + "text": "cannot assign to value: 'a' is a 'let' constant" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "index": 0 + }, + "replacements": [ + { + "deletedRegion": { + "endColumn": 4, + "endLine": 5, + "startColumn": 1, + "startLine": 5 + }, + "insertedContent": { + "text": "var" + } + } + ] + } + ], + "description": { + "text": "" + } + } + ], + "kind": "fail", + "level": "note", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "a", + "kind": "variable", + "name": "a" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 1, + "startLine": 5 + } + } + } + ], + "message": { + "text": "change 'let' to 'var' to make it mutable" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "b", + "kind": "variable", + "name": "b" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 14, + "startLine": 8 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "testLocation", + "kind": "function", + "name": "testLocation" + }, + { + "fullyQualifiedName": "testLocation.y", + "kind": "variable", + "name": "y", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 13, + "startLine": 16 + } + } + } + ], + "message": { + "text": "binary operator '+' cannot be applied to operands of type 'Int' and 'String'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "note", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "testLocation", + "kind": "function", + "name": "testLocation" + }, + { + "fullyQualifiedName": "testLocation.y", + "kind": "variable", + "name": "y", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 13, + "startLine": 16 + } + } + } + ], + "message": { + "text": "overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" + } + } + } + ], + "version": "2.1.0" +} diff --git a/test/sarif/Outputs/fixit.sarif b/test/sarif/Outputs/fixit.sarif deleted file mode 100644 index 8a4cfd3127c8a..0000000000000 --- a/test/sarif/Outputs/fixit.sarif +++ /dev/null @@ -1,144 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 15, - "endLine" : 18, - "startColumn" : 15, - "startLine" : 18 - }, - "insertedContent" : { - "text" : "x: <#Int#>, y: <#Int#>" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 14, - "startLine" : 18 - } - } - } - ], - "message" : { - "text" : "missing arguments for parameters 'x', 'y' in call" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 8, - "startLine" : 13 - } - } - } - ], - "message" : { - "text" : "'init(x:y:)' declared here" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 6, - "endLine" : 8, - "startColumn" : 3, - "startLine" : 8 - }, - "insertedContent" : { - "text" : "let" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "warning", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 7, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "variable 'x' was never mutated; consider changing to 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/fixits.sarif b/test/sarif/Outputs/fixits.sarif new file mode 100644 index 0000000000000..53c2b06fea159 --- /dev/null +++ b/test/sarif/Outputs/fixits.sarif @@ -0,0 +1,171 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "location": { + "uri": "/Volumes/avirals-external-drive-1/swift-personal/swift/test/sarif/fixits.swift" + }, + "mimeType": "text/x-swift", + "sourceLanguage": "swift" + } + ], + "results": [ + { + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "index": 0 + }, + "replacements": [ + { + "deletedRegion": { + "endColumn": 15, + "endLine": 16, + "startColumn": 15, + "startLine": 16 + }, + "insertedContent": { + "text": "x: <#Int#>, y: <#Int#>" + } + } + ] + } + ], + "description": { + "text": "" + } + } + ], + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "p", + "kind": "variable", + "name": "p" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 14, + "startLine": 16 + } + } + } + ], + "message": { + "text": "missing arguments for parameters 'x', 'y' in call" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "note", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "Point", + "kind": "struct", + "name": "Point" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 8, + "startLine": 11 + } + } + } + ], + "message": { + "text": "'init(x:y:)' declared here" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "index": 0 + }, + "replacements": [ + { + "deletedRegion": { + "endColumn": 6, + "endLine": 6, + "startColumn": 3, + "startLine": 6 + }, + "insertedContent": { + "text": "let" + } + } + ] + } + ], + "description": { + "text": "" + } + } + ], + "kind": "fail", + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "testFixIt", + "kind": "function", + "name": "testFixIt" + }, + { + "fullyQualifiedName": "testFixIt.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 7, + "startLine": 6 + } + } + } + ], + "message": { + "text": "variable 'x' was never mutated; consider changing to 'let' constant" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" + } + } + } + ], + "version": "2.1.0" +} diff --git a/test/sarif/Outputs/location.sarif b/test/sarif/Outputs/location.sarif deleted file mode 100644 index fbbabb155931d..0000000000000 --- a/test/sarif/Outputs/location.sarif +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 13, - "startLine" : 9 - } - } - } - ], - "message" : { - "text" : "binary operator '+' cannot be applied to operands of type 'Int' and 'String'" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 13, - "startLine" : 9 - } - } - } - ], - "message" : { - "text" : "overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/locations.sarif b/test/sarif/Outputs/locations.sarif new file mode 100644 index 0000000000000..edcd5ae8fe912 --- /dev/null +++ b/test/sarif/Outputs/locations.sarif @@ -0,0 +1,1107 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "location": { + "uri": "FILE[0]" + }, + "mimeType": "text/x-swift", + "sourceLanguage": "swift" + } + ], + "results": [ + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 16, + "startLine": 6 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyStruct", + "kind": "struct", + "name": "MyStruct" + }, + { + "fullyQualifiedName": "MyStruct.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 16, + "startLine": 39 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyActor", + "kind": "class", + "name": "MyActor" + }, + { + "fullyQualifiedName": "MyActor.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 16, + "startLine": 74 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "myMacro", + "kind": "macro", + "name": "myMacro" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 7, + "startLine": 148 + } + } + } + ], + "message": { + "text": "external macro implementation type 'MyMacros.MyMacro' could not be found for macro 'myMacro'; plugin for module 'MyMacros' not found" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "StructWithAccessors", + "kind": "struct", + "name": "StructWithAccessors" + }, + { + "fullyQualifiedName": "StructWithAccessors.value", + "kind": "variable", + "name": "value", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "StructWithAccessors.value.didSet", + "kind": "function", + "name": "didSet", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "StructWithAccessors.value.didSet.y", + "kind": "variable", + "name": "y", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 180 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.myMethod", + "kind": "function", + "name": "myMethod", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.myMethod.y", + "kind": "variable", + "name": "y", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 9 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.init", + "kind": "function", + "name": "init", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.init.z", + "kind": "variable", + "name": "z", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 13 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.deinit", + "kind": "function", + "name": "deinit", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.deinit.w", + "kind": "variable", + "name": "w", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 17 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.subscript", + "kind": "function", + "name": "subscript", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.subscript.s", + "kind": "variable", + "name": "s", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 21 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.computed", + "kind": "variable", + "name": "computed", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.computed.get", + "kind": "function", + "name": "get", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "MyClass.computed.get.c", + "kind": "variable", + "name": "c", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 27 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "class", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.computed", + "kind": "variable", + "name": "computed", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.computed.set", + "kind": "function", + "name": "set", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "MyClass.computed.set.d", + "kind": "variable", + "name": "d", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 31 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyStruct", + "kind": "struct", + "name": "MyStruct" + }, + { + "fullyQualifiedName": "MyStruct.myMethod", + "kind": "function", + "name": "myMethod", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyStruct.myMethod.y", + "kind": "variable", + "name": "y", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 42 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyStruct", + "kind": "struct", + "name": "MyStruct" + }, + { + "fullyQualifiedName": "MyStruct.init", + "kind": "function", + "name": "init", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyStruct.init.z", + "kind": "variable", + "name": "z", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 46 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyStruct", + "kind": "struct", + "name": "MyStruct" + }, + { + "fullyQualifiedName": "MyStruct.subscript", + "kind": "function", + "name": "subscript", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyStruct.subscript.s", + "kind": "variable", + "name": "s", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 50 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyEnum", + "kind": "enum", + "name": "MyEnum" + }, + { + "fullyQualifiedName": "MyEnum.myMethod", + "kind": "function", + "name": "myMethod", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyEnum.myMethod.y", + "kind": "variable", + "name": "y", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 62 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyEnum", + "kind": "enum", + "name": "MyEnum" + }, + { + "fullyQualifiedName": "MyEnum.computed", + "kind": "variable", + "name": "computed", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyEnum.computed.c", + "kind": "variable", + "name": "c", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 66 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyActor", + "kind": "class", + "name": "MyActor" + }, + { + "fullyQualifiedName": "MyActor.myMethod", + "kind": "function", + "name": "myMethod", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyActor.myMethod.y", + "kind": "variable", + "name": "y", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 77 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyActor", + "kind": "class", + "name": "MyActor" + }, + { + "fullyQualifiedName": "MyActor.init", + "kind": "function", + "name": "init", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyActor.init.z", + "kind": "variable", + "name": "z", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 81 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "MyClass", + "kind": "extension", + "name": "MyClass" + }, + { + "fullyQualifiedName": "MyClass.extMethod", + "kind": "function", + "name": "extMethod", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "MyClass.extMethod.x", + "kind": "variable", + "name": "x", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 98 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "OuterClass", + "kind": "class", + "name": "OuterClass" + }, + { + "fullyQualifiedName": "OuterClass.NestedClass", + "kind": "class", + "name": "NestedClass", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "OuterClass.NestedClass.nestedMethod", + "kind": "function", + "name": "nestedMethod", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "OuterClass.NestedClass.nestedMethod.x", + "kind": "variable", + "name": "x", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 107 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "OuterClass", + "kind": "class", + "name": "OuterClass" + }, + { + "fullyQualifiedName": "OuterClass.NestedStruct", + "kind": "struct", + "name": "NestedStruct", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "OuterClass.NestedStruct.nestedMethod", + "kind": "function", + "name": "nestedMethod", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "OuterClass.NestedStruct.nestedMethod.y", + "kind": "variable", + "name": "y", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 113 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "OuterClass", + "kind": "class", + "name": "OuterClass" + }, + { + "fullyQualifiedName": "OuterClass.NestedEnum", + "kind": "enum", + "name": "NestedEnum", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "OuterClass.NestedEnum.nestedMethod", + "kind": "function", + "name": "nestedMethod", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "OuterClass.NestedEnum.nestedMethod.z", + "kind": "variable", + "name": "z", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 121 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "+++", + "kind": "function", + "name": "+++" + }, + { + "fullyQualifiedName": "+++.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 16, + "startLine": 135 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "topLevelFunction", + "kind": "function", + "name": "topLevelFunction" + }, + { + "fullyQualifiedName": "topLevelFunction.x", + "kind": "variable", + "name": "x", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 16, + "startLine": 153 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "outerFunction", + "kind": "function", + "name": "outerFunction" + }, + { + "fullyQualifiedName": "outerFunction.innerFunction", + "kind": "function", + "name": "innerFunction", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "outerFunction.innerFunction.x", + "kind": "variable", + "name": "x", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 160 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "functionWithClosure", + "kind": "function", + "name": "functionWithClosure" + }, + { + "fullyQualifiedName": "functionWithClosure.closure", + "kind": "variable", + "name": "closure", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "functionWithClosure.closure.x", + "kind": "variable", + "name": "x", + "parentIndex": 1 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 18, + "startLine": 168 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + }, + { + "kind": "fail", + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "StructWithAccessors", + "kind": "struct", + "name": "StructWithAccessors" + }, + { + "fullyQualifiedName": "StructWithAccessors.value", + "kind": "variable", + "name": "value", + "parentIndex": 0 + }, + { + "fullyQualifiedName": "StructWithAccessors.value.willSet", + "kind": "function", + "name": "willSet", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "StructWithAccessors.value.willSet.x", + "kind": "variable", + "name": "x", + "parentIndex": 2 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 20, + "startLine": 177 + } + } + } + ], + "message": { + "text": "cannot convert value of type 'String' to specified type 'Int'" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" + } + } + } + ], + "version": "2.1.0" +} diff --git a/test/sarif/Outputs/multiple-errors.sarif b/test/sarif/Outputs/multiple-errors.sarif deleted file mode 100644 index a100241a069c9..0000000000000 --- a/test/sarif/Outputs/multiple-errors.sarif +++ /dev/null @@ -1,117 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'a' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 7, - "startColumn" : 1, - "startLine" : 7 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 7 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 14, - "startLine" : 10 - } - } - } - ], - "message" : { - "text" : "cannot convert value of type 'String' to specified type 'Int'" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/notes.sarif b/test/sarif/Outputs/notes.sarif index 38b3195dc34c5..3c69d50606070 100644 --- a/test/sarif/Outputs/notes.sarif +++ b/test/sarif/Outputs/notes.sarif @@ -1,48 +1,55 @@ { - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ { - "artifacts" : [ + "artifacts": [ { - "location" : { - "uri" : "FILE[0]" + "location": { + "uri": "/Volumes/avirals-external-drive-1/swift-personal/swift/test/sarif/notes.swift" }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" + "mimeType": "text/x-swift", + "sourceLanguage": "swift" } ], - "results" : [ + "results": [ { - "kind" : "fail", - "level" : "error", - "locations" : [ + "kind": "fail", + "level": "error", + "locations": [ { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 + "logicalLocations": [ + { + "fullyQualifiedName": "testNotes", + "kind": "function", + "name": "testNotes" + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 }, - "region" : { - "startColumn" : 7, - "startLine" : 13 + "region": { + "startColumn": 7, + "startLine": 11 } } } ], - "message" : { - "text" : "function is unused" + "message": { + "text": "function is unused" }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" } ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" } } } ], - "version" : "2.1.0" + "version": "2.1.0" } diff --git a/test/sarif/Outputs/sarif-json-extension.sarif.json b/test/sarif/Outputs/sarif-json-extension.sarif.json index d46c111d8c6dd..ad78af70d7da5 100644 --- a/test/sarif/Outputs/sarif-json-extension.sarif.json +++ b/test/sarif/Outputs/sarif-json-extension.sarif.json @@ -65,6 +65,13 @@ "level": "note", "locations": [ { + "logicalLocations": [ + { + "fullyQualifiedName": "x", + "kind": "variable", + "name": "x" + } + ], "physicalLocation": { "artifactLocation": { "index": 0 diff --git a/test/sarif/Outputs/schema-compliance.sarif b/test/sarif/Outputs/schema-compliance.sarif deleted file mode 100644 index 93f56900d3a5d..0000000000000 --- a/test/sarif/Outputs/schema-compliance.sarif +++ /dev/null @@ -1,96 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'x' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 7, - "startColumn" : 1, - "startLine" : 7 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 7 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/severity-mapping.sarif b/test/sarif/Outputs/severity-mapping.sarif deleted file mode 100644 index 7e7e8654e9021..0000000000000 --- a/test/sarif/Outputs/severity-mapping.sarif +++ /dev/null @@ -1,144 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "kind" : "fail", - "level" : "error", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "cannot assign to value: 'a' is a 'let' constant" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 4, - "endLine" : 7, - "startColumn" : 1, - "startLine" : 7 - }, - "insertedContent" : { - "text" : "var" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "note", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 1, - "startLine" : 7 - } - } - } - ], - "message" : { - "text" : "change 'let' to 'var' to make it mutable" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - }, - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 8, - "endLine" : 11, - "startColumn" : 3, - "startLine" : 11 - }, - "insertedContent" : { - "text" : "_" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "warning", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 7, - "startLine" : 11 - } - } - } - ], - "message" : { - "text" : "initialization of variable 'x' was never used; consider replacing with assignment to '_' or removing it" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/warning.sarif b/test/sarif/Outputs/warning.sarif deleted file mode 100644 index 8d8c3e834586b..0000000000000 --- a/test/sarif/Outputs/warning.sarif +++ /dev/null @@ -1,75 +0,0 @@ -{ - "$schema" : "https://json.schemastore.org/sarif-2.1.0.json", - "runs" : [ - { - "artifacts" : [ - { - "location" : { - "uri" : "FILE[0]" - }, - "mimeType" : "text/x-swift", - "sourceLanguage" : "swift" - } - ], - "results" : [ - { - "fixes" : [ - { - "artifactChanges" : [ - { - "artifactLocation" : { - "index" : 0 - }, - "replacements" : [ - { - "deletedRegion" : { - "endColumn" : 16, - "endLine" : 8, - "startColumn" : 3, - "startLine" : 8 - }, - "insertedContent" : { - "text" : "_" - } - } - ] - } - ], - "description" : { - "text" : "" - } - } - ], - "kind" : "fail", - "level" : "warning", - "locations" : [ - { - "physicalLocation" : { - "artifactLocation" : { - "index" : 0 - }, - "region" : { - "startColumn" : 7, - "startLine" : 8 - } - } - } - ], - "message" : { - "text" : "initialization of variable 'unusedVar' was never used; consider replacing with assignment to '_' or removing it" - }, - "ruleId" : "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" - } - ], - "tool" : { - "driver" : { - "informationUri" : "https://swift.org", - "name" : "Swift Compiler", - "organization" : "Swift Project", - "version" : "6.3" - } - } - } - ], - "version" : "2.1.0" -} diff --git a/test/sarif/Outputs/warnings.sarif b/test/sarif/Outputs/warnings.sarif new file mode 100644 index 0000000000000..957d3590a195d --- /dev/null +++ b/test/sarif/Outputs/warnings.sarif @@ -0,0 +1,88 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "location": { + "uri": "/Volumes/avirals-external-drive-1/swift-personal/swift/test/sarif/warnings.swift" + }, + "mimeType": "text/x-swift", + "sourceLanguage": "swift" + } + ], + "results": [ + { + "fixes": [ + { + "artifactChanges": [ + { + "artifactLocation": { + "index": 0 + }, + "replacements": [ + { + "deletedRegion": { + "endColumn": 16, + "endLine": 6, + "startColumn": 3, + "startLine": 6 + }, + "insertedContent": { + "text": "_" + } + } + ] + } + ], + "description": { + "text": "" + } + } + ], + "kind": "fail", + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "testWarning", + "kind": "function", + "name": "testWarning" + }, + { + "fullyQualifiedName": "testWarning.unusedVar", + "kind": "variable", + "name": "unusedVar", + "parentIndex": 0 + } + ], + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "startColumn": 7, + "startLine": 6 + } + } + } + ], + "message": { + "text": "initialization of variable 'unusedVar' was never used; consider replacing with assignment to '_' or removing it" + }, + "ruleId": "MessageID(domain: \"SwiftCompiler\", id: \"SimpleDiagnostic\")" + } + ], + "tool": { + "driver": { + "informationUri": "https://swift.org", + "name": "Swift Compiler", + "organization": "Swift Project", + "version": "6.3" + } + } + } + ], + "version": "2.1.0" +} diff --git a/test/sarif/artifact-index.swift b/test/sarif/artifact-index.swift deleted file mode 100644 index 7a9acb89748b5..0000000000000 --- a/test/sarif/artifact-index.swift +++ /dev/null @@ -1,14 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/artifact-index.sarif %t/output.sarif %s - -// Test artifact index usage - all diagnostics should reference by index not URI - -let a = 5 -a = 10 // error 1 - -let b = 3 -b = 20 // error 2 - -let c = 7 -c = 30 // error 3 diff --git a/test/sarif/basic-error.swift b/test/sarif/basic-error.swift deleted file mode 100644 index 2f5afc2264b63..0000000000000 --- a/test/sarif/basic-error.swift +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/basic-error.sarif %t/output.sarif %s - -// Test basic error diagnostic output in SARIF format - -let x = 5 -x = 10 // error: cannot assign to value: 'x' is a 'let' constant diff --git a/test/sarif/multiple-errors.swift b/test/sarif/errors.swift similarity index 70% rename from test/sarif/multiple-errors.swift rename to test/sarif/errors.swift index 3b5e9e1e9c52e..7eb612caa8dd1 100644 --- a/test/sarif/multiple-errors.swift +++ b/test/sarif/errors.swift @@ -1,8 +1,6 @@ // RUN: %empty-directory(%t) // RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/multiple-errors.sarif %t/output.sarif %s - -// Test multiple errors in SARIF format +// RUN: %S/validate-sarif.py %S/Outputs/errors.sarif %t/output.sarif %s let a = 5 a = 10 // error: cannot assign to value: 'a' is a 'let' constant @@ -12,3 +10,8 @@ let b: Int = "string" // error: cannot convert value of type 'String' to specif func foo() -> Int { // error: missing return in a function expected to return 'Int' } + +func testLocation() { + let x = 5 + let y = x + "string" // error at line 8, column 17 +} diff --git a/test/sarif/fixit.swift b/test/sarif/fixits.swift similarity index 80% rename from test/sarif/fixit.swift rename to test/sarif/fixits.swift index d35f22d4c541a..b874f709ccfd5 100644 --- a/test/sarif/fixit.swift +++ b/test/sarif/fixits.swift @@ -1,8 +1,6 @@ // RUN: %empty-directory(%t) // RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/fixit.sarif %t/output.sarif %s - -// Test Fix-It suggestions in SARIF format +// RUN: %S/validate-sarif.py %S/Outputs/fixits.sarif %t/output.sarif %s func testFixIt() { var x: Int diff --git a/test/sarif/location.swift b/test/sarif/location.swift deleted file mode 100644 index 76b903cb9279e..0000000000000 --- a/test/sarif/location.swift +++ /dev/null @@ -1,10 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/location.sarif %t/output.sarif %s - -// Test location information in SARIF format - -func testLocation() { - let x = 5 - let y = x + "string" // error at line 8, column 17 -} diff --git a/test/sarif/locations.swift b/test/sarif/locations.swift new file mode 100644 index 0000000000000..0a05518c6555b --- /dev/null +++ b/test/sarif/locations.swift @@ -0,0 +1,183 @@ +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 +// RUN: %S/validate-sarif.py %S/Outputs/locations.sarif %t/output.sarif %s + +class MyClass { + let x: Int = "wrong" // Error in class property + + func myMethod() { + let y: Int = "wrong" // Error in class method + } + + init() { + let z: Int = "wrong" // Error in class initializer + } + + deinit { + let w: Int = "wrong" // Error in class deinitializer + } + + subscript(index: Int) -> String { + let s: Int = "wrong" // Error in class subscript + return "test" + } + + var computed: Int { + get { + let c: Int = "wrong" // Error in class property getter + return 0 + } + set { + let d: Int = "wrong" // Error in class property setter + } + } +} + +// MARK: - Struct contexts + +struct MyStruct { + let x: Int = "wrong" // Error in struct property + + func myMethod() { + let y: Int = "wrong" // Error in struct method + } + + init() { + let z: Int = "wrong" // Error in struct initializer + } + + subscript(index: Int) -> String { + let s: Int = "wrong" // Error in struct subscript + return "test" + } +} + +// MARK: - Enum contexts + +enum MyEnum { + case first + case second(Int) + + func myMethod() { + let y: Int = "wrong" // Error in enum method + } + + var computed: Int { + let c: Int = "wrong" // Error in enum property + return 0 + } +} + +// MARK: - Actor contexts + +actor MyActor { + let x: Int = "wrong" // Error in actor property + + func myMethod() { + let y: Int = "wrong" // Error in actor method + } + + init() { + let z: Int = "wrong" // Error in actor initializer + } +} + +// MARK: - Protocol contexts + +protocol MyProtocol { + associatedtype Item + + func myMethod() + var myProperty: Int { get } +} + +// MARK: - Extension contexts + +extension MyClass { + func extMethod() { + let x: Int = "wrong" // Error in extension method + } +} + +// MARK: - Nested type contexts + +class OuterClass { + class NestedClass { + func nestedMethod() { + let x: Int = "wrong" // Error in nested class method + } + } + + struct NestedStruct { + func nestedMethod() { + let y: Int = "wrong" // Error in nested struct method + } + } + + enum NestedEnum { + case value + + func nestedMethod() { + let z: Int = "wrong" // Error in nested enum method + } + } +} + +// MARK: - Type alias contexts + +typealias MyAlias = String + +// MARK: - Operator contexts + +infix operator +++ + +func +++ (lhs: Int, rhs: Int) -> Int { + let x: Int = "wrong" // Error in operator function + return lhs + rhs +} + +// MARK: - Precedence group contexts + +precedencegroup MyPrecedence { + higherThan: AdditionPrecedence +} + +// MARK: - Macro contexts (if supported) + +@freestanding(expression) +macro myMacro(_ value: T) -> T = #externalMacro(module: "MyMacros", type: "MyMacro") + +// MARK: - Free functions (top-level) + +func topLevelFunction() { + let x: Int = "wrong" // Error in top-level function +} + +// MARK: - Nested functions + +func outerFunction() { + func innerFunction() { + let x: Int = "wrong" // Error in nested function + } +} + +// MARK: - Closures within functions + +func functionWithClosure() { + let closure = { + let x: Int = "wrong" // Error in closure (may not generate logical location) + } +} + +// MARK: - Property with accessor + +struct StructWithAccessors { + var value: Int { + willSet { + let x: Int = "wrong" // Error in willSet accessor + } + didSet { + let y: Int = "wrong" // Error in didSet accessor + } + } +} diff --git a/test/sarif/notes.swift b/test/sarif/notes.swift index b77cebff42be7..302e185a51a5f 100644 --- a/test/sarif/notes.swift +++ b/test/sarif/notes.swift @@ -2,8 +2,6 @@ // RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 // RUN: %S/validate-sarif.py %S/Outputs/notes.sarif %t/output.sarif %s -// Test note diagnostics (related locations) in SARIF format - class MyClass { func myMethod() {} } diff --git a/test/sarif/schema-compliance.swift b/test/sarif/schema-compliance.swift deleted file mode 100644 index 5cba3c6e689b3..0000000000000 --- a/test/sarif/schema-compliance.swift +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/schema-compliance.sarif %t/output.sarif %s - -// Test SARIF schema compliance and structure validation - -let x = 5 -x = 10 // error diff --git a/test/sarif/severity-mapping.swift b/test/sarif/severity-mapping.swift deleted file mode 100644 index 3ea2185e86569..0000000000000 --- a/test/sarif/severity-mapping.swift +++ /dev/null @@ -1,12 +0,0 @@ -// RUN: %empty-directory(%t) -// RUN: not %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/severity-mapping.sarif %t/output.sarif %s - -// Test severity mapping (error, warning, note) to SARIF levels - -let a = 5 -a = 10 // error - -func testWarning() { - var x = 42 // warning: unused -} diff --git a/test/sarif/validate-sarif.py b/test/sarif/validate-sarif.py index be8c4bb30ba27..8771cdf57ea31 100755 --- a/test/sarif/validate-sarif.py +++ b/test/sarif/validate-sarif.py @@ -8,8 +8,13 @@ import sys def readfile(path): - with open(path) as f: - return f.read() + data = "" + try: + with open(path) as f: + data = f.read() + except Exception as e: + data = f"[\"Error: {e}\"]" + return data def process_sarif(sarif_expected_file, sarif_actual_file, swift_input_files): sarif_expected_str = readfile(sarif_expected_file) diff --git a/test/sarif/warning.swift b/test/sarif/warnings.swift similarity index 63% rename from test/sarif/warning.swift rename to test/sarif/warnings.swift index 2eeb8be15c905..4baffafa6f510 100644 --- a/test/sarif/warning.swift +++ b/test/sarif/warnings.swift @@ -1,8 +1,6 @@ // RUN: %empty-directory(%t) // RUN: %target-swift-frontend -typecheck %s -serialize-diagnostics-path %t/output.sarif 2>&1 -// RUN: %S/validate-sarif.py %S/Outputs/warning.sarif %t/output.sarif %s - -// Test warning diagnostic output in SARIF format +// RUN: %S/validate-sarif.py %S/Outputs/warnings.sarif %t/output.sarif %s func testWarning() { var unusedVar = 42 // warning: variable 'unusedVar' was never used