@gspothq/cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -11
- package/configurations/database/postgres/manifest.toml +51 -57
- package/configurations/database/postgres/rules/POSTGRES.md +14 -13
- package/configurations/framework/astro/manifest.toml +33 -35
- package/configurations/framework/astro/rules/ASTRO.md +2 -2
- package/configurations/framework/expo/manifest.toml +19 -19
- package/configurations/framework/express/manifest.toml +10 -4
- package/configurations/framework/express/rules/EXPRESS.md +9 -7
- package/configurations/framework/fastapi/manifest.toml +9 -3
- package/configurations/framework/fastapi/rules/FASTAPI.md +19 -18
- package/configurations/framework/nestjs/manifest.toml +12 -12
- package/configurations/framework/nestjs/rules/NESTJS.md +9 -9
- package/configurations/framework/nextjs/manifest.toml +39 -79
- package/configurations/framework/nextjs/rules/NEXTJS.md +9 -7
- package/configurations/framework/nextjs/tool-files/semgrep/nextjs.yml +35 -0
- package/configurations/framework/react/manifest.toml +33 -43
- package/configurations/framework/react/rules/REACT.md +14 -20
- package/configurations/framework/react-dom/manifest.toml +6 -6
- package/configurations/framework/react-native/manifest.toml +11 -11
- package/configurations/framework/react-native/rules/REACT-NATIVE.md +3 -5
- package/configurations/framework/site/manifest.toml +57 -110
- package/configurations/framework/site/rules/SITE.md +1 -1
- package/configurations/framework/svelte/manifest.toml +29 -31
- package/configurations/framework/svelte/rules/SVELTE.md +6 -6
- package/configurations/framework/vue/manifest.toml +37 -37
- package/configurations/framework/vue/rules/VUE.md +6 -6
- package/configurations/general/commits/manifest.toml +20 -84
- package/configurations/general/commits/rules/GIT.md +24 -13
- package/configurations/general/coverage/manifest.toml +1 -1
- package/configurations/general/dependencies/manifest.toml +47 -107
- package/configurations/general/dependencies/rules/DEPENDENCIES.md +13 -7
- package/configurations/general/docs/manifest.toml +28 -43
- package/configurations/general/docs/rules/DOCS.md +16 -9
- package/configurations/general/duplication/manifest.toml +12 -14
- package/configurations/general/engineering/manifest.toml +6 -2
- package/configurations/general/engineering/rules/agent/PLANNING.md +9 -6
- package/configurations/general/engineering/rules/agent/SUPPRESSIONS.md +20 -11
- package/configurations/general/engineering/rules/agent/TASKS.md +6 -2
- package/configurations/general/engineering/rules/agent/WORKING.md +34 -29
- package/configurations/general/engineering/rules/code/ACCESSIBILITY.md +9 -7
- package/configurations/general/engineering/rules/code/CLI.md +12 -9
- package/configurations/general/engineering/rules/code/COMMENTS.md +11 -1
- package/configurations/general/engineering/rules/code/CONFIGURATION.md +2 -0
- package/configurations/general/engineering/rules/code/ERRORS.md +5 -3
- package/configurations/general/engineering/rules/code/HTTP.md +9 -10
- package/configurations/general/engineering/rules/code/LOGGING.md +10 -9
- package/configurations/general/engineering/rules/code/TESTING.md +18 -11
- package/configurations/general/files/manifest.toml +45 -73
- package/configurations/general/files/rules/YAML.md +20 -10
- package/configurations/general/format/manifest.toml +53 -106
- package/configurations/general/gspot/manifest.toml +19 -39
- package/configurations/general/gspot/rules/GENERATED.md +14 -4
- package/configurations/general/licenses/manifest.toml +12 -14
- package/configurations/general/naming/defaults.json +14 -13
- package/configurations/general/naming/manifest.toml +17 -17
- package/configurations/general/naming/rules/NAMING.md +87 -67
- package/configurations/general/prose/manifest.toml +166 -25
- package/configurations/general/prose/rules/WRITING.md +6 -2
- package/configurations/general/repository/manifest.toml +12 -12
- package/configurations/general/secrets/manifest.toml +52 -73
- package/configurations/general/secrets/rules/SECRETS.md +3 -1
- package/configurations/general/security/manifest.toml +29 -52
- package/configurations/general/security/rules/SECURITY.md +8 -6
- package/configurations/general/spelling/manifest.toml +15 -25
- package/configurations/general/structure/manifest.toml +31 -31
- package/configurations/infrastructure/ansible/manifest.toml +15 -15
- package/configurations/infrastructure/docker/manifest.toml +55 -67
- package/configurations/infrastructure/docker/rules/DOCKER.md +15 -13
- package/configurations/infrastructure/github-actions/manifest.toml +21 -41
- package/configurations/infrastructure/github-actions/rules/GITHUB-ACTIONS.md +16 -12
- package/configurations/infrastructure/github-actions/tool-files/zizmor.yml +5 -0
- package/configurations/infrastructure/nginx/manifest.toml +20 -20
- package/configurations/infrastructure/nginx/rules/NGINX.md +5 -4
- package/configurations/infrastructure/openapi/manifest.toml +20 -20
- package/configurations/infrastructure/openapi/rules/OPENAPI.md +3 -1
- package/configurations/infrastructure/xcode/manifest.toml +55 -75
- package/configurations/infrastructure/xcode/rules/XCODE.md +5 -3
- package/configurations/language/bash/manifest.toml +89 -170
- package/configurations/language/bash/rules/BASH.md +79 -17
- package/configurations/language/bash/rules/LANGUAGE.md +30 -26
- package/configurations/language/bash/rules/OPERATIONS.md +10 -8
- package/configurations/language/bash/rules/SAFETY.md +39 -35
- package/configurations/language/css/manifest.toml +22 -61
- package/configurations/language/css/rules/CSS.md +21 -2
- package/configurations/language/css/rules/TAILWIND.md +7 -5
- package/configurations/language/html/manifest.toml +16 -35
- package/configurations/language/html/rules/HTML.md +23 -5
- package/configurations/language/javascript/eslint-runtime-names.json +66 -61
- package/configurations/language/javascript/eslint-runtime.mjs +118 -0
- package/configurations/language/javascript/manifest.toml +135 -207
- package/configurations/language/javascript/rules/BUN.md +2 -2
- package/configurations/language/javascript/rules/COMPONENTS.md +4 -4
- package/configurations/language/javascript/rules/DENO.md +1 -1
- package/configurations/language/javascript/rules/JAVASCRIPT.md +30 -17
- package/configurations/language/javascript/rules/NODE.md +2 -2
- package/configurations/language/javascript/rules/PLAYWRIGHT.md +10 -9
- package/configurations/language/markdown/manifest.toml +20 -51
- package/configurations/language/python/manifest.toml +231 -342
- package/configurations/language/python/rules/DESIGN.md +40 -33
- package/configurations/language/python/rules/FLOW.md +8 -6
- package/configurations/language/python/rules/PACKAGING.md +14 -7
- package/configurations/language/python/rules/PYTHON.md +83 -33
- package/configurations/language/python/rules/TYPING.md +25 -23
- package/configurations/language/sql/manifest.toml +19 -37
- package/configurations/language/sql/rules/SQL.md +90 -2
- package/configurations/language/swift/manifest.toml +67 -78
- package/configurations/language/swift/rules/SWIFT.md +138 -40
- package/configurations/language/swift/rules/SWIFTUI.md +11 -9
- package/configurations/language/swift/rules/UIKIT.md +21 -19
- package/configurations/language/typescript/manifest.toml +34 -34
- package/configurations/language/typescript/rules/TYPESCRIPT.md +62 -10
- package/configurations/library/drizzle/manifest.toml +25 -38
- package/configurations/library/drizzle/rules/DRIZZLE.md +4 -2
- package/configurations/library/react-hook-form/manifest.toml +4 -4
- package/configurations/library/react-hook-form/rules/REACT-HOOK-FORM.md +15 -13
- package/configurations/library/tanstack-query/manifest.toml +6 -6
- package/configurations/library/tanstack-query/rules/TANSTACK-QUERY.md +5 -3
- package/configurations/library/testing-library/manifest.toml +6 -6
- package/configurations/library/translations/manifest.toml +22 -22
- package/configurations/library/translations/rules/NEXT-INTL.md +6 -8
- package/configurations/library/translations/rules/TRANSLATIONS.md +22 -23
- package/configurations/library/trpc/manifest.toml +3 -3
- package/configurations/library/trpc/rules/TRPC.md +8 -6
- package/configurations/library/zod/manifest.toml +6 -6
- package/configurations/library/zod/rules/ZOD.md +4 -2
- package/configurations/library/zustand/manifest.toml +4 -4
- package/configurations/library/zustand/rules/ZUSTAND.md +16 -14
- package/configurations/platform/cloudflare/manifest.toml +28 -47
- package/configurations/platform/cloudflare/rules/CLOUDFLARE.md +11 -6
- package/configurations/platform/supabase/manifest.toml +48 -61
- package/configurations/platform/supabase/rules/SUPABASE.md +27 -28
- package/configurations/testing/jest/manifest.toml +21 -22
- package/configurations/testing/pytest/manifest.toml +28 -28
- package/configurations/testing/swift-tests/manifest.toml +18 -29
- package/configurations/testing/swift-tests/rules/SWIFT-TESTS.md +7 -5
- package/configurations/testing/vitest/manifest.toml +32 -32
- package/configurations/testing/vitest/rules/VITEST.md +3 -1
- package/dist/gspot.js +22297 -24707
- package/dist/{linkinator-program.js → linkinator-process.js} +5 -7
- package/dist/{purgecss-program.js → purgecss-process.js} +12 -12
- package/package.json +7 -9
- package/configurations/infrastructure/github-actions/zizmor.yml +0 -3
- package/configurations/language/bash/rules/NAMING.md +0 -62
- package/configurations/language/css/rules/NAMING.md +0 -17
- package/configurations/language/html/rules/NAMING.md +0 -16
- package/configurations/language/javascript/rules/NAMING.md +0 -31
- package/configurations/language/markdown/markdownlint-cli2.mjs +0 -19
- package/configurations/language/python/rules/NAMING.md +0 -53
- package/configurations/language/sql/rules/NAMING.md +0 -90
- package/configurations/language/swift/rules/NAMING.md +0 -88
- package/configurations/language/typescript/rules/NAMING.md +0 -73
- package/configurations/library/swift-snapshot-testing/manifest.toml +0 -32
- package/dist/THIRD-PARTY-NOTICES.md +0 -25
- package/dist/eslint-rule-levels.json +0 -10
- package/dist/tool-versions.json +0 -3
- package/gspot.schema.json +0 -45991
- /package/{dist/LICENSE.md → LICENSE.md} +0 -0
- /package/configurations/framework/express/{semgrep → tool-files/semgrep}/express.yml +0 -0
- /package/configurations/framework/fastapi/{semgrep → tool-files/semgrep}/fastapi.yml +0 -0
- /package/configurations/general/dependencies/{syncpack.json → tool-files/syncpack.json} +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/LICENSES.md +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ampm.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/conscious-language.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/contractions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/currency.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dashes.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/date-ordinals.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/dates.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/file-paths.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/future.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/hedging.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/history.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/idioms.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/interface-verbs.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/jargon.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/leading-conjunctions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/link-text.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/locations.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/marketing.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/merge-conflict-markers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/militaristic-language.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/modals.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/numbered-units.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/obvious-fields.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/ordinal-adverbs.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/percentage-numbers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/placeholders.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/product-centric-writing.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/punctuated-headings.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/self-reference.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/session-identifiers.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/since.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/straight-quotes.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/suspended-hyphens.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/symbols.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/time-ranges.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/url-prepositions.yml +0 -0
- /package/configurations/general/prose/{vale → tool-files/vale}/styles/gspot/version-range.yml +0 -0
- /package/configurations/general/secrets/{gitleaks.toml → tool-files/gitleaks.toml} +0 -0
- /package/configurations/infrastructure/docker/{trivyignore → tool-files/trivyignore} +0 -0
- /package/configurations/language/bash/{semgrep → tool-files/semgrep}/bash.yml +0 -0
- /package/configurations/language/javascript/{semgrep → tool-files/semgrep}/javascript.yml +0 -0
- /package/configurations/language/swift/{semgrep → tool-files/semgrep}/swift.yml +0 -0
- /package/configurations/platform/cloudflare/{semgrep → tool-files/semgrep}/cloudflare.yml +0 -0
- /package/configurations/platform/supabase/{semgrep → tool-files/semgrep}/supabase.yml +0 -0
|
@@ -1,9 +1,97 @@
|
|
|
1
1
|
# SQL
|
|
2
2
|
|
|
3
|
+
Read this rule when you write SQL.
|
|
4
|
+
|
|
3
5
|
## SQL style
|
|
4
6
|
|
|
5
7
|
- Fully qualify cross-schema references.
|
|
6
|
-
- Prefer explicit column lists for
|
|
8
|
+
- Prefer explicit column lists for each `INSERT` and every grant of write
|
|
7
9
|
privileges.
|
|
8
|
-
- Keep table constraints close to the
|
|
10
|
+
- Keep table constraints close to the definition unless the migration must add them
|
|
9
11
|
later for dependency reasons.
|
|
12
|
+
|
|
13
|
+
## Naming
|
|
14
|
+
|
|
15
|
+
### SQL case rules
|
|
16
|
+
|
|
17
|
+
<!-- level: all -->
|
|
18
|
+
|
|
19
|
+
- SQL schemas, tables, columns, functions, parameters, indexes,
|
|
20
|
+
triggers, constraints, and policies use `lower_snake_case`.
|
|
21
|
+
- Fully qualify grants and function signatures when names have more than one possible meaning.
|
|
22
|
+
- Do not use quoted sentence-style identifiers for local policy names.
|
|
23
|
+
- Do not use pluralization or prefixes inconsistently inside one schema.
|
|
24
|
+
|
|
25
|
+
| Avoid | Prefer | Meaning |
|
|
26
|
+
| ---------------------------- | -------------------------------- | ---------------------------------- |
|
|
27
|
+
| `public.OrderItems` | `public.order_items` | An order-item table. |
|
|
28
|
+
| `users_can_view_order_items` | `users_can_view_own_order_items` | A policy restricted to owned rows. |
|
|
29
|
+
|
|
30
|
+
The policy name must describe what its predicate checks. A name that says "own"
|
|
31
|
+
does not establish ownership checks or enable Row Level Security.
|
|
32
|
+
|
|
33
|
+
### Migration filenames
|
|
34
|
+
|
|
35
|
+
<!-- level: all -->
|
|
36
|
+
|
|
37
|
+
Follow the migration tool's file names. Name each migration for its durable database change.
|
|
38
|
+
|
|
39
|
+
### SQL tables and columns
|
|
40
|
+
|
|
41
|
+
<!-- level: all -->
|
|
42
|
+
|
|
43
|
+
- Table names identify the domain set stored by the relation.
|
|
44
|
+
- Column names identify the value, not the application layer that reads it.
|
|
45
|
+
- Foreign-key columns use the referenced concept plus `_id`.
|
|
46
|
+
- Timestamp columns use stable names such as `created_at`,
|
|
47
|
+
`updated_at`, `deleted_at`, or a concrete domain event time.
|
|
48
|
+
- Boolean columns use concise positive assertion names without an `is_`
|
|
49
|
+
prefix.
|
|
50
|
+
- Avoid generic columns that hide meaning.
|
|
51
|
+
|
|
52
|
+
| Avoid | Prefer | Meaning |
|
|
53
|
+
| --------- | --------------------------- | ------------------------- |
|
|
54
|
+
| `data` | `message_delivery_attempts` | Delivery attempt table. |
|
|
55
|
+
| `payload` | `provider_response` | Response payload column. |
|
|
56
|
+
| `flag` | `retryable` | Retry eligibility column. |
|
|
57
|
+
| `date` | `attempted_at` | Attempt timestamp column. |
|
|
58
|
+
|
|
59
|
+
### SQL functions and parameters
|
|
60
|
+
|
|
61
|
+
<!-- level: all -->
|
|
62
|
+
|
|
63
|
+
- SQL function names use verb phrases or domain operations. These verbs apply to functions,
|
|
64
|
+
not migration filenames.
|
|
65
|
+
|
|
66
|
+
- Retrieval functions use `get_` whether they return one row,
|
|
67
|
+
an optional one, a collection, or a paginated one. Do not use
|
|
68
|
+
`read_`, `find_`, `fetch_`, `load_`, or `list_` as retrieval synonyms.
|
|
69
|
+
- Data-access mutation functions use `set_`, `insert_`, `update_`, or
|
|
70
|
+
`delete_` according to the operation they perform. Do not use `create_`,
|
|
71
|
+
`add_`, `save_`, `write_`, `put_`, `upsert_`, or `remove_` as synonyms.
|
|
72
|
+
- Parameter names never collide with table columns.
|
|
73
|
+
- `SECURITY DEFINER` functions must have names that make the privileged action
|
|
74
|
+
clear.
|
|
75
|
+
- Do not name functions like arbitrary script tasks.
|
|
76
|
+
|
|
77
|
+
Use `archive_expired_orders` for a transactional operation that archives
|
|
78
|
+
expired orders. Name its user argument `target_user_id` when the affected tables already have a `user_id` column. A generic `run` name hides the
|
|
79
|
+
operation, and an unqualified `id` can obscure column ownership.
|
|
80
|
+
|
|
81
|
+
### Indexes, constraints, triggers, and policies
|
|
82
|
+
|
|
83
|
+
<!-- level: all -->
|
|
84
|
+
|
|
85
|
+
- Name constraints and scope them to their table or concept.
|
|
86
|
+
- Name indexes by table plus their columns or purpose.
|
|
87
|
+
- Name triggers by event and action.
|
|
88
|
+
- Name policies by actor plus allowed action.
|
|
89
|
+
- Keep names stable because they appear in migrations, errors, grants, and
|
|
90
|
+
database inspection output.
|
|
91
|
+
|
|
92
|
+
| Avoid | Prefer | Meaning |
|
|
93
|
+
| --------------- | --------------------------------- | ------------------------------------ |
|
|
94
|
+
| `idx1` | `messages_user_id_created_at_idx` | Index on user and creation time. |
|
|
95
|
+
| `check_status` | `messages_status_not_empty_check` | Nonempty status constraint. |
|
|
96
|
+
| `trigger1` | `messages_set_updated_at` | Update timestamp trigger. |
|
|
97
|
+
| `select_policy` | `users_can_view_own_messages` | Policy restricted to owned messages. |
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
allowed_words = ["Swift", "SwiftLint", "SwiftFormat", "Periphery"]
|
|
2
|
-
|
|
3
2
|
ignored_folders = [".build", "DerivedData", "Pods"]
|
|
4
3
|
|
|
5
4
|
[configuration]
|
|
6
|
-
|
|
5
|
+
description = "Swift sources: SwiftLint, including its analyzer, with stable rules for each level, SwiftFormat, a build, and Periphery for dead code."
|
|
7
6
|
suggests = []
|
|
8
|
-
|
|
7
|
+
title = "Swift"
|
|
9
8
|
|
|
10
9
|
[detect]
|
|
11
10
|
extensions = [".swift"]
|
|
@@ -15,150 +14,140 @@ project_files = ["Package.swift"]
|
|
|
15
14
|
extensions = [".swift"]
|
|
16
15
|
filenames = ["Package.swift", "Package.resolved"]
|
|
17
16
|
|
|
17
|
+
[rules]
|
|
18
|
+
"SWIFTUI.md" = { tags = ["swift-import:SwiftUI"] }
|
|
19
|
+
"UIKIT.md" = { tags = ["swift-import:UIKit"] }
|
|
20
|
+
|
|
18
21
|
[[tool]]
|
|
22
|
+
brew = "swiftlint"
|
|
23
|
+
builds = ["macos", "linux"]
|
|
24
|
+
github = { name = "realm/SwiftLint", version = "0.63.2" }
|
|
25
|
+
min_version = "0.57.0"
|
|
26
|
+
mise = "swiftlint"
|
|
19
27
|
name = "swiftlint"
|
|
28
|
+
replace = [".swiftlint.yml", ".swiftlint.yaml"]
|
|
20
29
|
rule_url = "https://realm.github.io/SwiftLint/{rule}.html"
|
|
21
30
|
suppression = { marker = "\\bswiftlint:disable\\b", reason = " - (?<reason>\\S.*)" }
|
|
22
|
-
builds = ["macos", "linux"]
|
|
23
31
|
version = "0.63.2"
|
|
24
|
-
min_version = "0.57.0"
|
|
25
|
-
mise = "swiftlint"
|
|
26
|
-
brew = "swiftlint"
|
|
27
|
-
github = { name = "realm/SwiftLint", version = "0.63.2" }
|
|
28
32
|
version_command = ["version"]
|
|
29
|
-
[[tool.replace]]
|
|
30
|
-
file = ".swiftlint.yml"
|
|
31
|
-
shared = false
|
|
32
|
-
|
|
33
|
-
[[tool.replace]]
|
|
34
|
-
file = ".swiftlint.yaml"
|
|
35
|
-
shared = false
|
|
36
33
|
|
|
37
34
|
[[tool]]
|
|
35
|
+
builds = ["macos", "linux"]
|
|
36
|
+
min_version = "0.54.0"
|
|
38
37
|
name = "swiftformat"
|
|
38
|
+
replace = [".swiftformat"]
|
|
39
39
|
suppression = { marker = "\\bswiftformat:disable\\b", reason = " - (?<reason>\\S.*)" }
|
|
40
|
-
builds = ["macos", "linux"]
|
|
41
40
|
version = "0.61.1"
|
|
42
|
-
min_version = "0.54.0"
|
|
43
41
|
# The GitHub release ships an app bundle beside the executable, which mise skips.
|
|
44
|
-
mise = { name = "swiftformat", version = "0.61.1", options = { no_app = true, rename_exe = "swiftformat" } }
|
|
45
42
|
brew = "swiftformat"
|
|
46
43
|
github = { name = "nicklockwood/SwiftFormat", version = "0.61.1" }
|
|
47
|
-
|
|
48
|
-
file = ".swiftformat"
|
|
49
|
-
shared = false
|
|
44
|
+
mise = { name = "swiftformat", version = "0.61.1", options = { no_app = true, rename_exe = "swiftformat" } }
|
|
50
45
|
|
|
51
46
|
[[tool]]
|
|
52
|
-
|
|
53
|
-
suppression = { marker = "\\bperiphery:ignore\\b", reason = " - (?<reason>\\S.*)" }
|
|
47
|
+
brew = "periphery"
|
|
54
48
|
builds = ["macos"]
|
|
55
|
-
version = "3.6.0"
|
|
49
|
+
github = { name = "peripheryapp/periphery", version = "3.6.0" }
|
|
56
50
|
min_version = "3.0.0"
|
|
57
51
|
mise = "periphery"
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
name = "periphery"
|
|
53
|
+
replace = [".periphery.yml", ".periphery.yaml"]
|
|
54
|
+
suppression = { marker = "\\bperiphery:ignore\\b", reason = " - (?<reason>\\S.*)" }
|
|
55
|
+
version = "3.6.0"
|
|
60
56
|
version_command = ["version"]
|
|
61
|
-
[[tool.replace]]
|
|
62
|
-
file = ".periphery.yml"
|
|
63
|
-
shared = false
|
|
64
|
-
|
|
65
|
-
[[tool.replace]]
|
|
66
|
-
file = ".periphery.yaml"
|
|
67
|
-
shared = false
|
|
68
57
|
|
|
69
58
|
[[tool]]
|
|
70
59
|
name = "swift"
|
|
71
60
|
system = true
|
|
72
61
|
|
|
73
62
|
[[tool]]
|
|
74
|
-
name = "xcodebuild"
|
|
75
63
|
install_hint = "install Xcode from the App Store"
|
|
64
|
+
name = "xcodebuild"
|
|
76
65
|
system = true
|
|
77
66
|
|
|
78
67
|
[[tool_file]]
|
|
79
|
-
target = ".gspot/config/swiftlint.yml"
|
|
80
|
-
tool = "swiftlint"
|
|
81
|
-
rule_keys = ["opt_in_rules", "analyzer_rules", "disabled_rules"]
|
|
82
68
|
per_scope = true
|
|
83
69
|
pointer = { path = ".swiftlint.yml", body = "parent_config: {target_json}\n" }
|
|
70
|
+
target = ".gspot/config/swiftlint.yml"
|
|
71
|
+
tool = "swiftlint"
|
|
84
72
|
|
|
85
73
|
[[tool_file]]
|
|
74
|
+
per_scope = true
|
|
86
75
|
target = ".gspot/config/swiftformat"
|
|
87
76
|
tool = "swiftformat"
|
|
88
|
-
rule_keys = ["enable", "disable"]
|
|
89
|
-
per_scope = true
|
|
90
77
|
|
|
91
78
|
[[tool_file]]
|
|
79
|
+
per_scope = true
|
|
92
80
|
target = ".gspot/config/periphery.yml"
|
|
93
81
|
tool = "periphery"
|
|
82
|
+
|
|
83
|
+
[[tool_file]]
|
|
94
84
|
per_scope = true
|
|
85
|
+
target = ".gspot/config/semgrep/swift.yml"
|
|
86
|
+
tool = "semgrep"
|
|
87
|
+
when = { configuration = "security" }
|
|
95
88
|
|
|
96
89
|
[[check]]
|
|
97
|
-
name = "swiftlint"
|
|
98
|
-
example = "The forced cast `value as! NSString` reports `force_cast`. Use a conditional cast and handle the failed conversion, or change the input contract so no cast is required."
|
|
99
|
-
level = "recommended"
|
|
100
|
-
stage = "commit"
|
|
101
|
-
tool = "swiftlint"
|
|
102
90
|
command = ["swiftlint", "lint", "--strict", "--quiet", "--no-cache", "--reporter", "json", "{files}"]
|
|
103
|
-
fix = ["swiftlint", "lint", "--fix", "--quiet", "--no-cache", "{files}"]
|
|
104
91
|
cwd = "scope"
|
|
92
|
+
example = "`value as! NSString` reports `force_cast`. Use `as?` and handle the failure, or change the input type so no cast remains."
|
|
105
93
|
files = { extensions = [".swift"] }
|
|
94
|
+
fix = ["swiftlint", "lint", "--fix", "--quiet", "--no-cache", "{files}"]
|
|
95
|
+
help = "Run `gspot check --fix` for the rules that fix themselves. Turn one rule off with `gspot ignore swift/swiftlint --rule <id> --reason \"<why>\"`."
|
|
96
|
+
level = "recommended"
|
|
97
|
+
name = "swiftlint"
|
|
106
98
|
output = { format = "json", fields = { file = "file", line = "line", column = "character", rule = "rule_id", message = "reason" } }
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
stage = "commit"
|
|
100
|
+
summary = "Lints Swift files with the SwiftLint files of each scope and nested folder, and treats warnings as errors."
|
|
101
|
+
tool = "swiftlint"
|
|
102
|
+
why = "Forced casts and tries can crash, and a strong delegate can keep its owner alive."
|
|
110
103
|
|
|
111
104
|
[[check]]
|
|
112
|
-
name = "swiftformat"
|
|
113
|
-
example = "A declaration beginning `func greeting` reports `consecutiveSpaces`. Run the configured formatter to obtain `func greeting`, inspect the diff, and rerun."
|
|
114
|
-
level = "recommended"
|
|
115
|
-
stage = "commit"
|
|
116
105
|
command = ["swiftformat", "--lint", "--config", "{tool_file:swiftformat}", "{files}"]
|
|
106
|
+
example = "`func greeting` reports `consecutiveSpaces`. Run `gspot check --fix` to get `func greeting`."
|
|
117
107
|
exit_codes = [1]
|
|
118
|
-
fix = ["swiftformat", "--quiet", "--config", "{tool_file:swiftformat}", "{files}"]
|
|
119
108
|
files = { extensions = [".swift"] }
|
|
109
|
+
fix = ["swiftformat", "--quiet", "--config", "{tool_file:swiftformat}", "{files}"]
|
|
110
|
+
help = "Run `gspot check --fix`."
|
|
111
|
+
level = "recommended"
|
|
112
|
+
name = "swiftformat"
|
|
120
113
|
output = { format = "regex", pattern = "^(?<file>.+?):(?<line>\\d+):(?<column>\\d+): (?:error|warning): \\((?<rule>\\w+)\\) (?<message>.*)$" }
|
|
114
|
+
stage = "commit"
|
|
121
115
|
summary = "Checks the layout of every Swift file against the shipped SwiftFormat rules."
|
|
122
116
|
why = "One layout everywhere keeps a diff about the change and not about where a brace went."
|
|
123
|
-
help = "Run gspot check --fix."
|
|
124
117
|
|
|
125
118
|
[[check]]
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
example = "Assigning a string to an `Int` variable fails the build. Correct the value or the type."
|
|
120
|
+
help = "Fix the first compiler error; later ones often come from the same mistake."
|
|
128
121
|
level = "recommended"
|
|
129
|
-
|
|
130
|
-
runs = "scope"
|
|
122
|
+
name = "build"
|
|
131
123
|
needs = ["build"]
|
|
132
124
|
operating_systems = ["macos", "linux"]
|
|
125
|
+
runs = "scope"
|
|
126
|
+
stage = "push"
|
|
133
127
|
summary = "Builds the package or Xcode scheme incrementally to report compiler errors."
|
|
134
|
-
why = "Swift
|
|
135
|
-
help = "Fix the first compiler error; later ones are often the same mistake."
|
|
128
|
+
why = "Swift checks types only during a build."
|
|
136
129
|
|
|
137
130
|
[[check]]
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
example = "An import that no compiled Swift source uses reports a finding. Remove the import."
|
|
132
|
+
help = "Remove what the rule names. List an import that the analyzer flags in error under `tools.swiftlint.always_keep_imports`."
|
|
140
133
|
level = "recommended"
|
|
141
|
-
|
|
142
|
-
runs = "scope"
|
|
134
|
+
name = "swiftlint-analyze"
|
|
143
135
|
needs = ["build"]
|
|
144
136
|
operating_systems = ["macos"]
|
|
145
|
-
|
|
137
|
+
runs = "scope"
|
|
138
|
+
stage = "manual"
|
|
146
139
|
summary = "Runs the SwiftLint analyzer rules over the compiler log of the build: unused imports and captured variables."
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
tool = "swiftlint"
|
|
141
|
+
why = "These rules need compiler output, which a lint of single files lacks."
|
|
149
142
|
|
|
150
143
|
[[check]]
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
example = "A declaration that nothing references reports unused code. Delete it, and rebuild."
|
|
145
|
+
help = "Delete the declaration, or mark it with a periphery:ignore comment that says who uses it."
|
|
153
146
|
level = "recommended"
|
|
154
|
-
|
|
155
|
-
runs = "scope"
|
|
147
|
+
name = "periphery"
|
|
156
148
|
needs = ["build"]
|
|
157
|
-
|
|
149
|
+
runs = "scope"
|
|
150
|
+
stage = "push"
|
|
158
151
|
summary = "Scans the built project for declarations nothing uses."
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
[rules]
|
|
163
|
-
"SWIFTUI.md" = { tags = ["swift-import:SwiftUI"] }
|
|
164
|
-
"UIKIT.md" = { tags = ["swift-import:UIKit"] }
|
|
152
|
+
tool = "periphery"
|
|
153
|
+
why = "People read, maintain, and migrate dead code without knowing that nothing uses it."
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# Swift
|
|
2
2
|
|
|
3
|
+
Read this rule when you change Swift code.
|
|
4
|
+
|
|
3
5
|
Swift source follows the Apple API Design Guidelines and the local formatting and lint
|
|
4
6
|
configuration. SwiftFormat owns layout. Treat warnings as errors in the build settings.
|
|
5
7
|
|
|
6
8
|
## Files and style
|
|
7
9
|
|
|
8
|
-
Every file imports exactly the top-level modules it uses
|
|
10
|
+
Every file imports exactly the top-level modules it uses. Import whole modules unless a single
|
|
9
11
|
declaration avoids a known conflict. Group imports by compiler condition, with
|
|
10
|
-
`@testable import` after regular
|
|
11
|
-
|
|
12
|
-
needs explanation.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
`@testable import` after regular ones in tests. `// MARK: - Section` marks meaningful groups in larger files.
|
|
13
|
+
Write a header comment only where several abstractions share the file and their arrangement
|
|
14
|
+
needs explanation.
|
|
15
|
+
|
|
16
|
+
Rely on inference when the right-hand side has a clear type. Add an explicit annotation for
|
|
17
|
+
empty collections, `nil` initial values, weak type information, and public API
|
|
18
|
+
clarity. Document enum raw values that map to an external wire or persistence contract.
|
|
19
|
+
Replace `default` with an explicit list where a future case must force review. Attributes
|
|
16
20
|
with parameters go on their own line when they hurt readability inline.
|
|
17
21
|
|
|
18
22
|
### File organization
|
|
@@ -20,7 +24,7 @@ with parameters go on their own line when they hurt readability inline.
|
|
|
20
24
|
<!-- level: all -->
|
|
21
25
|
|
|
22
26
|
One primary top-level type per file, with small private helpers allowed beside it. Keep overloads adjacent. Organize extensions rather than scattering them across files. Group SwiftUI
|
|
23
|
-
dynamic properties by
|
|
27
|
+
dynamic properties by wrapper type.
|
|
24
28
|
|
|
25
29
|
Good:
|
|
26
30
|
|
|
@@ -42,53 +46,61 @@ public func comparePositiveTotals(_ left: [Int], _ right: [Int]) -> Int {
|
|
|
42
46
|
## Practices
|
|
43
47
|
|
|
44
48
|
Use `let` by default and `var` for required mutation. Class-constrained protocols use
|
|
45
|
-
`AnyObject`. Prefer shorthand types such as `[Element]`, `[Key: Value]`, or `Wrapped?`, and
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
`AnyObject`. Prefer shorthand types such as `[Element]`, `[Key: Value]`, or `Wrapped?`, and `Void` in function
|
|
50
|
+
signatures. Use an optional, not a sentinel, for a value that the program can omit. Compare it to `nil` when only presence
|
|
51
|
+
matters, bind it when the code uses the value, and chain it for one-off access.
|
|
48
52
|
|
|
49
53
|
Typed errors distinguish several meaningful failure states. A force unwrap or cast needs a
|
|
50
|
-
proven invariant.
|
|
51
|
-
|
|
52
|
-
mutable statics.
|
|
54
|
+
proven invariant. Avoid global mutable state: prefer `static let` or a computed `static var` to a
|
|
55
|
+
stored one.
|
|
53
56
|
|
|
54
|
-
`guard`
|
|
55
|
-
`
|
|
57
|
+
Use `guard` for early exits and `for ... where` for a loop with one condition. Prefer `for` to
|
|
58
|
+
`forEach` for control flow or async work. `map`, `compactMap`, and
|
|
56
59
|
`filter` express side-effect-free transformation. Pattern bindings put `let` on each element
|
|
57
|
-
where that avoids ambiguity.
|
|
58
|
-
|
|
59
|
-
only
|
|
60
|
-
|
|
60
|
+
where that avoids ambiguity.
|
|
61
|
+
|
|
62
|
+
Use weak capture only when a callback must not extend the lifetime of an object it holds.
|
|
63
|
+
Otherwise, capture the immutable values the callback needs. Use `#fileID` in production logs and `#filePath`
|
|
64
|
+
only in tests and tools. Overload operators only with their standard meaning. Define a
|
|
65
|
+
custom operator only for standard domain notation.
|
|
66
|
+
|
|
67
|
+
### Force unwraps
|
|
68
|
+
|
|
69
|
+
<!-- level: all -->
|
|
70
|
+
|
|
71
|
+
When the code needs a force unwrap, suppress `force_unwrapping` on that line with
|
|
72
|
+
`// swiftlint:disable:next force_unwrapping`. The reason states the invariant, as the suppression rules require.
|
|
61
73
|
|
|
62
74
|
### API and ownership conventions
|
|
63
75
|
|
|
64
76
|
<!-- level: all -->
|
|
65
77
|
|
|
66
78
|
Structs keep synthesized
|
|
67
|
-
memberwise initializers where sufficient
|
|
79
|
+
memberwise initializers where sufficient. Value types hold data without identity. Classes
|
|
68
80
|
serve identity, reference semantics, lifecycle, observable state, or framework requirements.
|
|
69
81
|
An implicitly unwrapped optional appears only for Apple lifecycle cases such as `@IBOutlet`,
|
|
70
82
|
Objective-C nullability gaps, and test data.
|
|
71
83
|
|
|
72
84
|
Declare top-level access to match the public contract. An extension carries no blanket access
|
|
73
|
-
modifier.
|
|
74
|
-
|
|
75
|
-
follows the standard library
|
|
76
|
-
|
|
77
|
-
images and colors
|
|
85
|
+
modifier. Nest a type when it makes sense only inside its parent. Use a caseless enum only for
|
|
86
|
+
closely related statics. Prefer methods and properties to free functions, except a symmetric
|
|
87
|
+
function that follows the standard library.
|
|
88
|
+
Send logs through the project logger, and take
|
|
89
|
+
images and colors from named assets or explicit constructors.
|
|
78
90
|
|
|
79
91
|
## Networking
|
|
80
92
|
|
|
81
|
-
URLs
|
|
82
|
-
|
|
93
|
+
Build URLs with `URLComponents` or a typed endpoint value, never concatenated query strings.
|
|
94
|
+
A production `URL(string:)!` documents its invariant. Inject base URLs and credentials
|
|
83
95
|
at composition. Use `Encodable` types for request bodies with an explicit method and
|
|
84
96
|
content type, never `[String: Any]`. `URLSession` succeeds for non-2xx responses.
|
|
85
|
-
Validate `HTTPURLResponse` status explicitly
|
|
97
|
+
Validate `HTTPURLResponse` status explicitly, and treat a non-HTTP response as a transport
|
|
86
98
|
failure.
|
|
87
99
|
|
|
88
|
-
Responses decode into DTOs mapped into domain values at the boundary. Validate required fields and
|
|
100
|
+
Responses decode into data transfer objects, or DTOs, mapped into domain values at the boundary. Validate required fields and
|
|
89
101
|
external enum values there. Do not invent a fallback ID, date, or case.
|
|
90
|
-
Clients expose typed errors
|
|
91
|
-
|
|
102
|
+
Clients expose typed errors for encoding, decoding, transport, an invalid response or status,
|
|
103
|
+
and cancellation. Cancellation stays distinct when navigation supersedes a
|
|
92
104
|
task. When you write tests, inject the transport and cover request construction, status validation,
|
|
93
105
|
decoding failure, cancellation, and mapping without a live service.
|
|
94
106
|
|
|
@@ -113,22 +125,108 @@ Structured `async`/`await` with `async throws` APIs replaces callback pyramids a
|
|
|
113
125
|
task trees. UI state mutates on the main actor. When the project uses view models, keep them
|
|
114
126
|
`@MainActor`.
|
|
115
127
|
|
|
116
|
-
Expensive work moves into use cases, repositories, actors, or background tasks. `Task`
|
|
117
|
-
in the modules that need
|
|
118
|
-
|
|
128
|
+
Expensive work moves into use cases, repositories, actors, or background tasks. Create a `Task` only
|
|
129
|
+
in the modules that need one: view models, coordinators, or views through `.task`, never in an initializer.
|
|
130
|
+
An object always holds and cancels it. Store handles when a disappearing view, a
|
|
119
131
|
retry, or a newer request must cancel older work. Check cancellation in long loops and
|
|
120
132
|
before publishing stale results.
|
|
121
133
|
|
|
122
134
|
Shared mutable state sits behind actors, main-actor
|
|
123
|
-
isolation, or explicit synchronization
|
|
124
|
-
`DispatchQueue.main.async`. The compiler verifies `Sendable` designs
|
|
125
|
-
concurrency annotations gets `@preconcurrency import
|
|
126
|
-
`nonisolated(unsafe)` appears only with a documented local invariant and no safer
|
|
135
|
+
isolation, or explicit synchronization. Prefer actor isolation to
|
|
136
|
+
`DispatchQueue.main.async`. The compiler verifies `Sendable` designs. A module without
|
|
137
|
+
concurrency annotations gets `@preconcurrency import`. `@unchecked Sendable` or
|
|
138
|
+
`nonisolated(unsafe)` appears only with a documented local invariant and no safer option.
|
|
127
139
|
Inject clocks and scheduling interfaces where time affects logic or tests.
|
|
128
140
|
|
|
129
141
|
Typed errors serve callers with different recovery paths, and untyped `Error` only generic
|
|
130
142
|
boundaries where cases add nothing. Technical errors become user-facing messages at the presentation
|
|
131
143
|
boundary. Failed operations throw, and UI loading and error display use explicit state enums.
|
|
132
144
|
Reserve `fatalError` for an unrecoverable programmer error with a documented invariant,
|
|
133
|
-
`assertionFailure` for an unexpected but recoverable state, and `precondition` for
|
|
145
|
+
`assertionFailure` for an unexpected but recoverable state, and `precondition` for a condition
|
|
134
146
|
that continuing requires.
|
|
147
|
+
|
|
148
|
+
## Naming
|
|
149
|
+
|
|
150
|
+
### Case, scope, and files
|
|
151
|
+
|
|
152
|
+
<!-- level: all -->
|
|
153
|
+
|
|
154
|
+
Preserve external names and domain notation that the team uses. Express privacy through access
|
|
155
|
+
modifiers, not an underscore.
|
|
156
|
+
|
|
157
|
+
Express a structural relationship through nesting rather than a longer
|
|
158
|
+
top-level name: `Parser.Error`, not `ParseError`. A static property does not repeat its
|
|
159
|
+
declaring type: `UIColor.primary`, not `UIColor.primaryColor`. Use `shared` and `default`
|
|
160
|
+
only when they describe the role.
|
|
161
|
+
|
|
162
|
+
### Roles
|
|
163
|
+
|
|
164
|
+
<!-- level: all -->
|
|
165
|
+
|
|
166
|
+
A suffix says what a type owns and means one thing across the whole codebase. If `Repository`
|
|
167
|
+
owns domain-facing data access in one feature, it never means HTTP mechanics in another.
|
|
168
|
+
Which suffixes a project uses depends on the pattern it picked, but these framework words have
|
|
169
|
+
one meaning:
|
|
170
|
+
|
|
171
|
+
| Role word | Use when |
|
|
172
|
+
| ---------------- | -------------------------------------------------------------------------------------- |
|
|
173
|
+
| `View` | SwiftUI or UIKit view type that presents UI. |
|
|
174
|
+
| `ViewController` | UIKit screen controller. |
|
|
175
|
+
| `ViewModel` | Holds presentation state and user intent for one screen, flow, or cohesive surface. |
|
|
176
|
+
| `ViewState` | Value describing screen presentation state. |
|
|
177
|
+
| `ViewAction` | Typed user or lifecycle intent sent by a view. |
|
|
178
|
+
| `Coordinator` | Holds navigation or flow state. |
|
|
179
|
+
| `Router` | Route mutation or destination-selection boundary, narrower than a coordinator. |
|
|
180
|
+
| `DataSource` | UIKit list adapter that feeds rows and sections. |
|
|
181
|
+
| `CellModel` | Values a reusable cell renders: strings, image references, state, and lightweight IDs. |
|
|
182
|
+
|
|
183
|
+
Repository protocols use domain language, results, and operations, such as
|
|
184
|
+
`TemplateRepository.getTemplate`. An implementation may name its technology, such as
|
|
185
|
+
`HTTPTemplateRepository`, when several real variants exist. Client types own
|
|
186
|
+
external API or SDK mechanics such as `TemplateAPIClient`. Coordinators own route state, destination
|
|
187
|
+
construction, stack mutations, and presentation flow such as `TemplateCoordinator.showEditTemplate`.
|
|
188
|
+
Keep route enums within their feature and name them for the flow. Give them stable domain identifiers
|
|
189
|
+
such as `TemplateDestination.editTemplate`, never view models, views, repository implementations, SDK
|
|
190
|
+
clients, database records, or DTOs.
|
|
191
|
+
|
|
192
|
+
### Methods, labels, and delegates
|
|
193
|
+
|
|
194
|
+
<!-- level: all -->
|
|
195
|
+
|
|
196
|
+
When the project uses view models, name a direct UI event for what happened,
|
|
197
|
+
such as `submitButtonTapped()`, `cameraPermissionDenied()`, or `selectedItemChanged(to:)`. Name domain work
|
|
198
|
+
with a verb such as `enqueueFileUpload(_:)`, `refreshOrderHistory(for:)`, or
|
|
199
|
+
`validateEmailAddress(_:)`. `handle`, `process`, and `didTap` describe neither. Reserve `handle...` for
|
|
200
|
+
literal UIKit target-action and notification handlers marked `@objc`.
|
|
201
|
+
|
|
202
|
+
Call sites read as grammatical English: omit the first label when the base
|
|
203
|
+
name and argument form a phrase. Include it when it clarifies a weak type. Initializer
|
|
204
|
+
arguments that set stored properties take the same names with explicit `self.`.
|
|
205
|
+
Factories use `make...`, such as `makeTemplateView(for:)`. Nonmutating methods without side effects
|
|
206
|
+
read as noun phrases; mutating ones as imperatives. Pairs follow `sort`/`sorted`,
|
|
207
|
+
`append`/`appending`, and `formUnion`/`union`.
|
|
208
|
+
|
|
209
|
+
A delegate method passes the source object first and unlabeled, and never trims its signature because
|
|
210
|
+
one caller owns it. A source-only `Void` event combines the type name with a tense
|
|
211
|
+
phrase: `draftStore(_:didDeleteDraft:)`. A source-only `Bool` answer uses `can`, `is`, or
|
|
212
|
+
another predicate, never `should` or `may`: `draftStoreCanDeleteDraft`. Name a source-only value with a
|
|
213
|
+
noun phrase and a natural preposition: `numberOfSections(in:)`. Extra arguments make the
|
|
214
|
+
second label describe the event or requested value:
|
|
215
|
+
`messageListDataSource(_:heightForMessageAt:)`.
|
|
216
|
+
|
|
217
|
+
### Protocols and identifiers
|
|
218
|
+
|
|
219
|
+
<!-- level: all -->
|
|
220
|
+
|
|
221
|
+
A protocol carries no `I` prefix and no `Protocol` suffix. Name a domain role with a noun such as `TemplateRepository`. Name a capability for its behavior, often with `-ing`,
|
|
222
|
+
such as `ProgressReporting`, `ThemeColorProviding`, or `LoginCoordinating`. Avoid an automatic `-able`
|
|
223
|
+
name that describes no clear capability. `Provider`, `Repository`, `Client`, and
|
|
224
|
+
`Coordinating` appear only when the protocol serves that role. Do not add a protocol per ViewModel or use
|
|
225
|
+
case only to enable mocks.
|
|
226
|
+
|
|
227
|
+
Presentation identifiers provide stable interfaces for UI identity, diffable data sources,
|
|
228
|
+
navigation, persistence, and tests. Name them for what they identify, not for the framework
|
|
229
|
+
that consumes them. Snapshot items use stable presentation or domain IDs, never DTO identity,
|
|
230
|
+
array offsets, or index paths. `id` suffices when the enclosing type supplies the context,
|
|
231
|
+
such as `MessageRow.id`. A role-qualified name such as `selectedMessageID` or `conversationID`
|
|
232
|
+
serves a scope with several identifiers.
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# SwiftUI
|
|
2
2
|
|
|
3
|
+
Read this rule when you change SwiftUI views or state.
|
|
4
|
+
|
|
3
5
|
## Architecture
|
|
4
6
|
|
|
5
7
|
<!-- level: all -->
|
|
6
8
|
|
|
7
|
-
This rule picks no presentation pattern, such as Model-View-ViewModel (MVVM) or The Composable Architecture (TCA); a project that
|
|
8
|
-
|
|
9
|
+
This rule picks no presentation pattern, such as Model-View-ViewModel (MVVM) or The Composable Architecture (TCA); a project that adopts one
|
|
10
|
+
documents it.
|
|
9
11
|
|
|
10
12
|
## SwiftUI views
|
|
11
13
|
|
|
12
|
-
- Keep
|
|
14
|
+
- Keep state local when its lifetime matches the identity of the view.
|
|
13
15
|
Use `@State`, `@FocusState`, `@GestureState`, and bindings according to their
|
|
14
16
|
ownership and read contracts.
|
|
15
17
|
- Keep view-owned state properties private. Inject values owned by a parent
|
|
16
18
|
through the appropriate input or binding.
|
|
17
19
|
- Preserve read and actor isolation when state changes.
|
|
18
|
-
- Do not store derived
|
|
20
|
+
- Do not store derived values in `@State` when you can compute them from the source state.
|
|
19
21
|
- Avoid heavy computation in `body`; SwiftUI can evaluate it repeatedly.
|
|
20
22
|
- Preserve cancellation and stale-result handling for lifecycle work started with
|
|
21
23
|
`.task`, `.onAppear`, or refresh actions.
|
|
@@ -24,9 +26,9 @@ down.
|
|
|
24
26
|
|
|
25
27
|
<!-- level: all -->
|
|
26
28
|
|
|
27
|
-
- Views render state and forward user intent to the project
|
|
28
|
-
- Keep business workflows, network
|
|
29
|
-
construction in
|
|
29
|
+
- Views render state and forward user intent to the project operation or the object that holds it.
|
|
30
|
+
- Keep business workflows, network and database calls, and concrete infrastructure
|
|
31
|
+
construction in the types that handle them.
|
|
30
32
|
- Do not add explicit `@ViewBuilder` to `body`; the compiler provides it.
|
|
31
33
|
- Use `@ViewBuilder` for helper properties or functions that need conditional view branches.
|
|
32
34
|
- Omit `else { EmptyView() }` when the missing branch already produces no content.
|
|
@@ -39,9 +41,9 @@ down.
|
|
|
39
41
|
|
|
40
42
|
<!-- level: all -->
|
|
41
43
|
|
|
42
|
-
Keep screen and flow state in the project
|
|
44
|
+
Keep screen and flow state in the type the project chose for it. Use a shared store
|
|
43
45
|
when multiple screens need the same state. A store owns cohesive feature or domain
|
|
44
|
-
state
|
|
46
|
+
state. App composition holds only global state, such as
|
|
45
47
|
session, theme, or connectivity.
|
|
46
48
|
|
|
47
49
|
Prefer an enum for mutually exclusive loading states instead of parallel booleans.
|