From 8359d795ebf76cca465859e7b3d92b072b9889ce Mon Sep 17 00:00:00 2001 From: laosb Date: Wed, 20 Aug 2025 21:01:17 +0800 Subject: [PATCH] Improve source file detection and tests - Use SwiftPM-provided sourceFiles for filtering - Add Utils.swift with URL descendant check - Update example to use Views/ instead of Folder/ - Add test for non-included Swift file - Update .zed settings to disable VSCode CSS language server --- .zed/settings.json | 11 +++++++ .../TailwindCSS/TailwindCSSBuildPlugin.swift | 29 ++++++++++++++----- Plugins/TailwindCSS/Utils.swift | 24 +++++++++++++++ .../SwiftTailwindExample/NotIncluded.swift | 5 ++++ Sources/SwiftTailwindExample/Tailwind.css | 4 +-- .../{Folder => Views}/Template.swift | 0 .../{Folder => Views}/Test.html | 0 Tests/SwiftTailwindTests/Tests.swift | 4 +++ 8 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 .zed/settings.json create mode 100644 Plugins/TailwindCSS/Utils.swift create mode 100644 Sources/SwiftTailwindExample/NotIncluded.swift rename Sources/SwiftTailwindExample/{Folder => Views}/Template.swift (100%) rename Sources/SwiftTailwindExample/{Folder => Views}/Test.html (100%) diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..7c708be --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,11 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "languages": { + "CSS": { + "language_servers": ["!vscode-css-language-server"] + } + } +} diff --git a/Plugins/TailwindCSS/TailwindCSSBuildPlugin.swift b/Plugins/TailwindCSS/TailwindCSSBuildPlugin.swift index 03ff51b..fdf23e1 100644 --- a/Plugins/TailwindCSS/TailwindCSSBuildPlugin.swift +++ b/Plugins/TailwindCSS/TailwindCSSBuildPlugin.swift @@ -48,6 +48,10 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin { context: PluginContext, target: Target ) throws -> [Command] { + guard let sourceFileURLs = target.sourceModule?.sourceFiles.map({ $0.url }) else { + throw BuildError.notASourceModule + } + let tailwindCSSURL: URL = target.directoryURL.appending(component: "Tailwind.css") guard let cssContent = try? String(contentsOf: tailwindCSSURL) else { throw BuildError.missingTailwindCSSFile @@ -60,18 +64,25 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin { throw BuildError.sourceNotDeclarationUnsupported } - let sourcePaths = + let sourcePatterns = cssContent .matches(of: sourceDeclarationRegex) .compactMap { String($0.output.1) } - let sourceURLs: [URL] = sourcePaths.map { path in - // Simplified handling: If ** is used, we just include everything in the directory. + let sourcePatternURLs: [URL] = sourcePatterns.map { path in + // Simplified handling: If `**` is used, we just include everything in the directory. + // It's unlikely we will have the same glob processing logic as Tailwind CSS CLI, + // so we may as well just expand the coverage. + // This only affects SwiftPM change detection: Tailwind CSS CLI will handle the globbing correctly. let globlessPath = path.replacing(/\*\*.*/, with: "") return target.directoryURL .appending(component: globlessPath, directoryHint: .inferFromPath) .resolvingSymlinksInPath() } + let includedSourceURLs = sourceFileURLs.filter { file in + sourcePatternURLs.contains { file.isOrIsDescendant(of: $0) } + } + let tailwindCLIURL: URL = try context.tool(named: "tailwindcss").url let outputBundleURL = context.pluginWorkDirectoryURL .appending(component: outputBundleName, directoryHint: .isDirectory) @@ -79,9 +90,10 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin { component: outputCSSFilename, directoryHint: .notDirectory) print("Tailwind CSS Build Plugin") - print("Tailwind CSS File: \(tailwindCSSURL.path)") - print("@source declarations: \(sourcePaths)") - print("Source files: \(sourceURLs.map(\.path))") + print("Tailwind.css: \(tailwindCSSURL.path)") + print("@source declarations: \(sourcePatterns)") + print("All source files: \(sourceFileURLs.map(\.path))") + print("Input files: \(includedSourceURLs.map(\.path))") print("Output: \(outputURL.path)") return [ @@ -93,7 +105,7 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin { "--output", outputURL.path, "--minify", ], - inputFiles: [tailwindCSSURL] + sourceURLs, + inputFiles: [tailwindCSSURL] + includedSourceURLs, outputFiles: [outputBundleURL] ) ] @@ -102,12 +114,15 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin { extension TailwindCSSBuildPlugin { enum BuildError: Error { + case notASourceModule case missingTailwindCSSFile case missingImportStatement case sourceNotDeclarationUnsupported var localizedDescription: String { switch self { + case .notASourceModule: + "The target is not a source module." case .missingTailwindCSSFile: "Tailwind.css file not found in the target directory." case .missingImportStatement: diff --git a/Plugins/TailwindCSS/Utils.swift b/Plugins/TailwindCSS/Utils.swift new file mode 100644 index 0000000..f18bcdc --- /dev/null +++ b/Plugins/TailwindCSS/Utils.swift @@ -0,0 +1,24 @@ +import Foundation + +extension URL { + func isOrIsDescendant(of ancestor: URL) -> Bool { + guard ancestor.isFileURL, self.isFileURL else { + return false + } + + let ancestorComponents = ancestor.pathComponents + let selfComponents = self.pathComponents + + guard selfComponents.count >= ancestorComponents.count else { + return false + } + + for (index, component) in ancestorComponents.enumerated() { + if selfComponents[index] != component { + return false + } + } + + return true + } +} diff --git a/Sources/SwiftTailwindExample/NotIncluded.swift b/Sources/SwiftTailwindExample/NotIncluded.swift new file mode 100644 index 0000000..3cb7406 --- /dev/null +++ b/Sources/SwiftTailwindExample/NotIncluded.swift @@ -0,0 +1,5 @@ +// This file is not included in Tailwind.css, so TW classes here will not be generated. +let html = + """ + Swift + """ diff --git a/Sources/SwiftTailwindExample/Tailwind.css b/Sources/SwiftTailwindExample/Tailwind.css index 8fd853b..6b5a2bc 100644 --- a/Sources/SwiftTailwindExample/Tailwind.css +++ b/Sources/SwiftTailwindExample/Tailwind.css @@ -4,5 +4,5 @@ /** You must specify `source(none)` after `tailwindcss` import and add `@source` for each source file / folder you want to include. */ @import "tailwindcss" source(none); @source "./Example.swift"; -@source "./Folder"; -@source "./Folder/**/*.swift"; +@source "./Views"; +@source "./Views/**/*.swift"; diff --git a/Sources/SwiftTailwindExample/Folder/Template.swift b/Sources/SwiftTailwindExample/Views/Template.swift similarity index 100% rename from Sources/SwiftTailwindExample/Folder/Template.swift rename to Sources/SwiftTailwindExample/Views/Template.swift diff --git a/Sources/SwiftTailwindExample/Folder/Test.html b/Sources/SwiftTailwindExample/Views/Test.html similarity index 100% rename from Sources/SwiftTailwindExample/Folder/Test.html rename to Sources/SwiftTailwindExample/Views/Test.html diff --git a/Tests/SwiftTailwindTests/Tests.swift b/Tests/SwiftTailwindTests/Tests.swift index 008a9cf..8a69f4e 100644 --- a/Tests/SwiftTailwindTests/Tests.swift +++ b/Tests/SwiftTailwindTests/Tests.swift @@ -21,6 +21,10 @@ struct SwiftTailwindExampleTests { generatedCSS?.contains("bg-red-100") == true, "Arbitary value class used in Folder/Template.swift is generated." ) + #expect( + generatedCSS?.contains("text-sm") == false, + "Class used in other non-included Swift code is not generated." + ) #expect( generatedCSS?.contains("bg-blue-500") == false, "Class not used is not generated."