From b55fa8b761b0458fa2e4e1d2fba74c6ee826b626 Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Wed, 28 Oct 2026 00:32:47 +0100 Subject: [PATCH 1/3] Lint: the suite's shared SwiftLint and SwiftFormat config, and a CI lint job .swiftlint.yml and .swiftformat are copies of the suite's one config (edited there, copied by its sync tool). The fixes they asked for are whitespace, a duplicate import, redundant parentheses or semicolons; no number or name changes. CI's new lint job runs SwiftLint 0.65.1 (--strict) and SwiftFormat 0.64.4 (--lint) from pinned images. swift build --build-tests green. Part of mac4DSTEM/macEM-suite#61. Done: this repo lints from the shared config. Next: none here. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 10 ++++++ .swiftformat | 5 +++ .swiftlint.yml | 33 +++++++++++++++++++ .../Proposer/ElementProposer.swift | 1 - Sources/SpectroEELS/EELSComposition.swift | 2 +- Tests/SpectroEELSTests/EdgeWindowsTests.swift | 1 - .../EDSQuantPinsTests.swift | 1 - 7 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .swiftformat create mode 100644 .swiftlint.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f33cf6..1a73cee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,3 +69,13 @@ jobs: swift --version | grep -E "Swift version 6\.([2-9]|[1-9][0-9])" || { echo "Swift 6.2 or newer needed"; exit 1; } - name: Gate (tools/test.sh, release) run: tools/test.sh + + lint: + name: Lint (shared SwiftLint and SwiftFormat config) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: SwiftLint + run: docker run --rm -v "$PWD:/work" -w /work --entrypoint swiftlint ghcr.io/realm/swiftlint:0.65.1 lint --strict --quiet + - name: SwiftFormat + run: docker run --rm -v "$PWD:/work" -w /work --entrypoint swiftformat ghcr.io/nicklockwood/swiftformat:0.64.4 --lint . diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..475b591 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,5 @@ +# The suite's one SwiftFormat config. tools/lint/sync.sh copies it to every Swift repo as .swiftformat; edit it here only. +# CI runs `swiftformat --lint .`: whitespace and a few unambiguous fixes, nothing that reflows code. +--swiftversion 6.2 +--exclude .build,build,References,DerivedData +--rules trailingSpace,linebreakAtEndOfFile,consecutiveBlankLines,duplicateImports,semicolons,spaceAroundParens,emptyBraces,todos diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..0244b9e --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,33 @@ +# The suite's one SwiftLint config. tools/lint/sync.sh copies it to every Swift repo as .swiftlint.yml; edit it here only. +# Rules that catch a mistake or keep files tidy, all fixable without touching a number or a name. Not enabled, on purpose: +# identifier_name and type_name (the ported Python names: nx, q, E0), line_length (the house style runs to about 150 columns), +# force_unwrapping and force_try (tests and fixtures), identical_operands (`x != x` is a NaN test in numeric code), +# orphaned_doc_comment (a `// delete at` note between a doc comment and its declaration is the house idiom). +excluded: + - .build + - build + - References + - DerivedData +only_rules: + - duplicate_imports + - duplicate_enum_cases + - duplicated_key_in_dictionary_literal + - duplicate_conditions + - unused_optional_binding + - unused_setter_value + - discarded_notification_center_observer + - unneeded_override + - control_statement + - void_return + - trailing_whitespace + - trailing_newline + - leading_whitespace + - vertical_whitespace + - comment_spacing + - return_arrow_whitespace + - legacy_constructor + - legacy_cggeometry_functions + - legacy_nsgeometry_functions + - superfluous_disable_command + - blanket_disable_command + - invalid_swiftlint_command diff --git a/Sources/SpectroEDX/Spectroscopy/Proposer/ElementProposer.swift b/Sources/SpectroEDX/Spectroscopy/Proposer/ElementProposer.swift index 80efb99..8f1df1f 100644 --- a/Sources/SpectroEDX/Spectroscopy/Proposer/ElementProposer.swift +++ b/Sources/SpectroEDX/Spectroscopy/Proposer/ElementProposer.swift @@ -133,7 +133,6 @@ public nonisolated enum ProposerError: Error, Equatable { case cancelled } - public nonisolated struct ElementProposer: Sendable { package var currie: Currie /// The elements tested; nil is `defaultPool`. diff --git a/Sources/SpectroEELS/EELSComposition.swift b/Sources/SpectroEELS/EELSComposition.swift index 519b33a..89ddf8c 100644 --- a/Sources/SpectroEELS/EELSComposition.swift +++ b/Sources/SpectroEELS/EELSComposition.swift @@ -28,7 +28,7 @@ public enum EELSQuantification { var columns: [Int] = [] if let dup = symbols.enumerated().first(where: { i, s in symbols[..= 20) } - /// Where eXSpy's signed maximum stops early (the criterion_split cases: 3 and 4 elements), the default `.absolute` rule runs on /// and ends nearer the fixed point; the difference is bounded by the stopping error. Without these two cases the comparison above /// would hold trivially (every two-element set, and every other set here, stops on the same iteration under both rules). From 1b20386e90b1de3ace937f2ab8d4826a49ff0ff6 Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Wed, 28 Oct 2026 00:36:22 +0100 Subject: [PATCH 2/3] CI: the weekly Periphery scan keeps the "Periphery weekly scan" issue up to date A copy of the suite's template: Sundays and on demand, build, scan with --retain-public, update one open issue with the findings. Nothing is deleted by it. Part of mac4DSTEM/macEM-suite#61. Done: weekly unused-code scan in CI. Next: triage its first issue. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/periphery.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/periphery.yml diff --git a/.github/workflows/periphery.yml b/.github/workflows/periphery.yml new file mode 100644 index 0000000..1b34482 --- /dev/null +++ b/.github/workflows/periphery.yml @@ -0,0 +1,45 @@ +# Weekly unused-code scan (Periphery). A copy of the suite's template; edit it there. +# Builds the package and its tests, scans with --retain-public (a kit's public API is used by the apps), and keeps one +# open issue, "Periphery weekly scan", up to date with the findings. Findings are for triage; nothing is deleted here. +name: Periphery + +on: + schedule: + - cron: "17 3 * * 0" + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + scan: + runs-on: macos-26 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Install Periphery + run: brew install periphery && periphery version + - name: Build with the index store + run: swift build --build-system native --build-tests + - name: Scan + run: | + periphery scan --retain-public --skip-build --index-store-path .build/debug/index/store \ + --format csv --relative-results > periphery.csv + echo "count=$(( $(wc -l < periphery.csv) - 1 ))" >> "$GITHUB_ENV" + - name: Update the issue + env: + GH_TOKEN: ${{ github.token }} + run: | + title="Periphery weekly scan" + echo "Periphery $(periphery version), commit ${GITHUB_SHA::7}, $(date -u +%Y-%m-%d): **${count} findings** (run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})." > body.md + printf '\n| Kind | Name | Location | Hint |\n|---|---|---|---|\n' >> body.md + python3 - >> body.md <<'PY' + import csv + for r in list(csv.DictReader(open('periphery.csv')))[:100]: + print(f"| {r['Kind']} | `{r['Name']}` | {r['Location']} | {r['Hints']} |") + PY + [ "$count" -le 100 ] || echo "| … | $((count - 100)) more in the run's log | | |" >> body.md + cat periphery.csv + n=$(gh issue list --state open --search "in:title \"$title\"" --json number,title -q ".[] | select(.title == \"$title\") | .number" | head -1) + if [ -n "$n" ]; then gh issue edit "$n" --body-file body.md; else gh issue create --title "$title" --body-file body.md; fi From 55593640835f241bcdf8512c53c477f46a9467ce Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Thu, 29 Oct 2026 22:34:26 +0100 Subject: [PATCH 3/3] style: drop the trailing spaces SwiftFormat's lint flags in three SpectroEELS headers Co-Authored-By: Claude Opus 5.5 --- Sources/SpectroEELS/EdgeCrossSection.swift | 2 +- Sources/SpectroEELS/GOSTable.swift | 2 +- Sources/SpectroEELS/PowerLawBackground.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SpectroEELS/EdgeCrossSection.swift b/Sources/SpectroEELS/EdgeCrossSection.swift index d304d83..5bdb123 100644 --- a/Sources/SpectroEELS/EdgeCrossSection.swift +++ b/Sources/SpectroEELS/EdgeCrossSection.swift @@ -4,7 +4,7 @@ // computes it: the effective collection angle (`exspy/utils/eels/effective_angle.py:23-80`, Egerton 2nd ed. p. 420), // the q integration (`exspy/_misc/eels/base_gos.py`, `get_qaxis_and_gos` and `integrateq`), the interpolating // spline between tabulated energies and the power law beyond them (`components/_eels_cl_edge.py:436-494`). -// +// // // Precision as exSpy runs on NumPy 2.5.4: the GOS arrays are float32 and, by NEP 50, a Python float meeting a float32 // scalar or array is cast to float32, so the q limits, the GOS interpolated to q_max, the prefactor and the diff --git a/Sources/SpectroEELS/GOSTable.swift b/Sources/SpectroEELS/GOSTable.swift index 012f7b8..efc596f 100644 --- a/Sources/SpectroEELS/GOSTable.swift +++ b/Sources/SpectroEELS/GOSTable.swift @@ -2,7 +2,7 @@ // GOSTable.swift // Role: The generalised oscillator strength of one K shell, as exSpy 7185a4d1 reads it from the GOSH database // (`exspy/_misc/eels/gosh_gos.py`, `read_gos_data`; `base_gos.py`, `TabulatedGOS`), and the edges SpectroKit ships. -// +// // // The tables are the dft GOS of Segger, Guzzinati and Kohl (GOSH 1.5.0, DOI 10.5281/zenodo.7645765, CC-BY-4.0; md5 of the // file exSpy fetches 7fee8891c147a4f769668403b54c529b), the groups C/K1, N/K1 and O/K1, extracted by diff --git a/Sources/SpectroEELS/PowerLawBackground.swift b/Sources/SpectroEELS/PowerLawBackground.swift index ff7a2e2..3e309f8 100644 --- a/Sources/SpectroEELS/PowerLawBackground.swift +++ b/Sources/SpectroEELS/PowerLawBackground.swift @@ -3,7 +3,7 @@ // Role: The pre-edge background A E^-r by HyperSpy 2.4.0's two-area estimate (`_components/power_law.py:100-187`, // `PowerLaw.estimate_parameters`), evaluated as `remove_background(fast=True)` evaluates it on a binned axis // (`_signals/signal1d.py:1102-1152`; `models/model1d.py:375-390`: the component's value times the channel width). -// +// // // Departure from HyperSpy: where a window sum is not positive or A, r come out non-finite, // HyperSpy's `nan_to_num` (or, on a zero sum, the whole image's estimate failing) is replaced by a nil pixel with its