@sun-asterisk/sunlint 1.3.40 → 1.3.42
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/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/D001-recommended-lints.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D002-dispose-resources.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D003-prefer-widget-classes.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D004-avoid-shrinkwrap.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D005-widget-nesting.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D006-large-callbacks.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D007-lifecycle-order.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D008-long-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D009-function-parameters.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D010-cyclomatic-complexity.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D011-named-parameters.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D012-named-booleans.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D013-single-public-class.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D014-safe-collection-access.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D015-copywith-consistency.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D016-project-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D017-review-dependencies.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D018-no-commented-code.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D019-single-child-wrappers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D020-if-else-limit.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D021-negated-booleans.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D022-setstate-usage.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D023-unnecessary-overrides.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D024-avoid-unnecessary-statefulwidget.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D025-nested-ternaries.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB001-use-snake-case.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB002-use-camel-case.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB003-use-screaming-snake-case.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB004-predicate-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB005-dangerous-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB006-indentation.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB007-line-length.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB008-rescue-exception.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB009-save-bang.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB010-avoid-n-plus-one.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB011-use-find-each.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB012-sql-injection.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB013-prefer-has-many-through.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB014-dependent-associations.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB015-modern-validations.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB016-thin-controllers.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB017-avoid-fat-models.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB018-service-objects.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB019-avoid-metaprogramming.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB020-use-pluck.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB021-use-size.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB022-order-by-timestamps.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB023-where-missing.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB024-method-length.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB025-parameter-limits.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB026-avoid-deep-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB027-guard-clauses.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB028-class-length.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB029-meaningful-names.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB030-dry-principle.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB031-mvc-architecture.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB032-use-concerns.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB033-moderate-callbacks.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB034-use-decorators.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB035-comprehensive-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB036-frozen-string-literal.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB037-it-parameter.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB038-modern-enum-syntax.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB039-solid-adapters.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB040-rails-authentication.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB041-async-query-loading.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB042-hotwire-turbo.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB043-use-propshaft.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB044-structured-logging.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB045-prism-parser.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW001-block-based-kvo.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW002-class-delegate-protocol.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW003-compiler-protocol-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW004-contains-over-filter-count.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW005-convenience-type.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW006-discarded-notification-center-observer.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW007-discouraged-direct-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW008-discouraged-optional-boolean.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW009-empty-count.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW010-empty-string.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW011-explicit-init.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW012-fatal-error-message.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW013-for-where.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW014-force-cast.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW015-force-try.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW016-force-unwrapping.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW017-function-parameter-count.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW018-large-tuple.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW019-legacy-constructor.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW020-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW021-no-extension-access-modifier.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW022-overridden-super-call.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW023-override-in-extension.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW024-private-over-fileprivate.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW025-private-unit-test.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW026-prohibited-super-call.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW027-sorted-first-last.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW028-syntactic-sugar.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW029-unused-closure-parameter.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW030-unused-enumerated.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW031-unused-optional-binding.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW032-valid-ibinspectable.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW033-vertical-parameter-alignment.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW034-void-return.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW035-weak-delegate.md +28 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Follow MVC architecture strictly
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Maintain clear separation of concerns.
|
|
5
|
+
tags: rails, architecture, mvc, design
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Follow MVC architecture strictly
|
|
9
|
+
|
|
10
|
+
Maintain clear separation of concerns. Models should handle data and business logic, Views should only handle presentation, and Controllers should coordinate between them. Avoid business logic in views or controllers.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```erb
|
|
15
|
+
<%# View with business logic %>
|
|
16
|
+
<% if @user.orders.sum(&:price) > 1000 %>
|
|
17
|
+
<span class="premium">Premium</span>
|
|
18
|
+
<% end %>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct:**
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# Model
|
|
25
|
+
class User < ApplicationRecord
|
|
26
|
+
def premium?
|
|
27
|
+
orders.sum(&:price) > 1000
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# View
|
|
32
|
+
<% if @user.premium? %>
|
|
33
|
+
<span class="premium">Premium</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Tools:** Manual Review, Architectural Check
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use concerns judiciously
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Prevent concerns from becoming dumping grounds for unrelated code.
|
|
5
|
+
tags: rails, architecture, concerns, design
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use concerns judiciously
|
|
9
|
+
|
|
10
|
+
Prevent concerns from becoming dumping grounds for unrelated code. Only use concerns for clearly reusable behavior across multiple models/controllers. Each concern should have a single, well-defined responsibility.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# A concern that handles too many unrelated things
|
|
16
|
+
module UserHelper
|
|
17
|
+
extend ActiveSupport::Concern
|
|
18
|
+
# logic for auth, analytics, and image processing mixed together
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct:**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
module Authenticatable
|
|
26
|
+
extend ActiveSupport::Concern
|
|
27
|
+
# focus on auth logic only
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Tools:** Manual Review
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Document callbacks and use them sparingly
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Prevent hidden side effects and improve code clarity.
|
|
5
|
+
tags: rails, architecture, callbacks, documentation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Document callbacks and use them sparingly
|
|
9
|
+
|
|
10
|
+
Prevent hidden side effects and improve code clarity. Use callbacks (like `before_save`, `after_create`) only when necessary. Consider using service objects if a callback triggers complex business logic.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Hidden side effects):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class Order < ApplicationRecord
|
|
16
|
+
after_create :charge_credit_card # Dangerous side effect hidden in model lifecycle
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (Explicit action):**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class CheckoutService
|
|
24
|
+
def self.call(order)
|
|
25
|
+
order.save!
|
|
26
|
+
PaymentGateway.charge(order) # Explicit and easier to test
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Tools:** Manual Review
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use decorators for view logic
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Keep models and views clean by separating presentation logic.
|
|
5
|
+
tags: rails, architecture, decorator, presentation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use decorators for view logic
|
|
9
|
+
|
|
10
|
+
Keep models and views clean by separating presentation logic. Use decorators (e.g., using the Draper gem or simple delegate classes) to format model data for the view layer.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Logic in model):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User < ApplicationRecord
|
|
16
|
+
def full_name_for_display
|
|
17
|
+
"#{first_name} #{last_name} (#{email})"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (Logic in decorator):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class UserDecorator < Draper::Decorator
|
|
26
|
+
delegate_all
|
|
27
|
+
def display_name
|
|
28
|
+
"#{object.first_name} #{object.last_name} (#{object.email})"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Tools:** Manual Review
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Write comprehensive tests
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Ensure code quality and prevent regressions.
|
|
5
|
+
tags: ruby, quality, testing, stability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Write comprehensive tests
|
|
9
|
+
|
|
10
|
+
Ensure code quality and prevent regressions. Write unit tests for models/services, integration tests for controllers, and system tests for critical paths. Aim for >80% coverage for business-critical code.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# No tests covering edge cases or failure modes
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
RSpec.describe User, type: :model do
|
|
22
|
+
it "is valid with proper attributes" do
|
|
23
|
+
# ...
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "is invalid without an email" do
|
|
27
|
+
# ...
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** RSpec, Minitest, SimpleCov
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use frozen_string_literal: true
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Improve memory efficiency and prepare for Ruby 4.0 string immutability.
|
|
5
|
+
tags: ruby, performance, memory, preparation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use frozen_string_literal: true
|
|
9
|
+
|
|
10
|
+
Improve memory efficiency and prepare for Ruby 4.0 string immutability. Add `# frozen_string_literal: true` magic comment at the top of every Ruby file.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class MyClass
|
|
16
|
+
DEFAULT_STATUS = "pending" # New string object created every time
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# frozen_string_literal: true
|
|
24
|
+
class MyClass
|
|
25
|
+
DEFAULT_STATUS = "pending" # String object is frozen and reused
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** RuboCop (`Style/FrozenStringLiteralComment`)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use 'it' as a default block parameter (Ruby 3.4+)
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Enhance code readability for concise block operations.
|
|
5
|
+
tags: ruby, readability, quality, syntax
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use 'it' as a default block parameter (Ruby 3.4+)
|
|
9
|
+
|
|
10
|
+
Enhance code readability for concise block operations. In Ruby 3.4+, use the implicit `it` keyword instead of explicit single-use block parameters to make simple iterations cleaner.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
users.map { |user| user.name }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# Ruby 3.4+
|
|
22
|
+
users.map { it.name }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Tools:** RuboCop (`Style/ItAssignment`)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use modern hash-based enum syntax
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Use the more readable and explicit enum configuration in ActiveRecord.
|
|
5
|
+
tags: rails, active-record, readability, clarity
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use modern hash-based enum syntax
|
|
9
|
+
|
|
10
|
+
Use the more readable and explicit enum configuration in ActiveRecord. Prefer mapping values to database integers explicitly via a hash to prevent accidental shifts if elements are reordered.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class Order < ApplicationRecord
|
|
16
|
+
enum status: [:pending, :active, :shipped] # Dangerous if order changes
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class Order < ApplicationRecord
|
|
24
|
+
enum :status, { pending: 0, active: 1, shipped: 2 } # Explicit and safe
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** RuboCop (`Rails/EnumSyntax`)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Prefer Solid Adapters for Infrastructure (Rails 8+)
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Simplify deployment and reduce external dependencies.
|
|
5
|
+
tags: rails, rails8, architecture, infrastructure
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Prefer Solid Adapters for Infrastructure (Rails 8+)
|
|
9
|
+
|
|
10
|
+
Simplify deployment and reduce external dependencies. For Rails 8 projects, prefer `Solid Queue`, `Solid Cache`, and `Solid Cable` over Redis-based solutions for standard workloads to keep the infrastructure "lean".
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# config/environments/production.rb
|
|
16
|
+
config.cache_store = :redis_cache_store
|
|
17
|
+
config.active_job.queue_adapter = :sidekiq
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Rails 8 lean stack
|
|
24
|
+
config.cache_store = :solid_cache_store
|
|
25
|
+
config.active_job.queue_adapter = :solid_queue
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** Manual Review
|
|
29
|
+
---
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use built-in Rails 8 Authentication for greenfield projects
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Use the native, lightweight authentication system to reduce dependency on Devise.
|
|
5
|
+
tags: rails, rails8, authentication, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use built-in Rails 8 Authentication for greenfield projects
|
|
9
|
+
|
|
10
|
+
Use the native, lightweight authentication system to reduce dependency on Devise. Use `bin/rails generate authentication` for new projects unless complex features mandate a library.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Adding Devise by default without evaluating built-in needs
|
|
16
|
+
bundle add devise
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct:**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Using Rails 8 built-in generator
|
|
23
|
+
bin/rails generate authentication
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** Manual Review
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Async Query Loading for slow interactions
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve web performance by loading database data concurrently with view rendering.
|
|
5
|
+
tags: rails, performance, active-record, async
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Async Query Loading for slow interactions
|
|
9
|
+
|
|
10
|
+
Improve web performance by loading database data concurrently with view rendering. Use `.load_async` on ActiveRecord relations when multiple independent queries can be executed in the background while the controller or view processes.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Sequential execution (Sync)
|
|
16
|
+
@posts = Post.all.load
|
|
17
|
+
@users = User.all.load
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Parallel execution (Async)
|
|
24
|
+
@posts = Post.all.load_async
|
|
25
|
+
@users = User.all.load_async
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** Manual Review
|
|
29
|
+
---
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Minimize custom JavaScript with Hotwire/Turbo 2.0
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Focus on the Rails "Majestic Monolith" approach for better maintainability.
|
|
5
|
+
tags: rails, hotwire, turbo, stimulus, architecture
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Minimize custom JavaScript with Hotwire/Turbo 2.0
|
|
9
|
+
|
|
10
|
+
Focus on the Rails "Majestic Monolith" approach for better maintainability. Prefer `Turbo Frames` and `Turbo Streams` for interaction. Use `Stimulus` for small client-side behaviors.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
// Complex React/Vue component for a simple list update
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```erb
|
|
21
|
+
<%# Rails Turbo Stream response %>
|
|
22
|
+
<turbo-stream action="append" target="messages">
|
|
23
|
+
<template>
|
|
24
|
+
<%= render @message %>
|
|
25
|
+
</template>
|
|
26
|
+
</turbo-stream>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** Manual Review
|
|
30
|
+
---
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Propshaft as the default asset pipeline
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Use the modern, simplified asset pipeline instead of Sprockets.
|
|
5
|
+
tags: rails, assets, propshaft, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Propshaft as the default asset pipeline
|
|
9
|
+
|
|
10
|
+
Use the modern, simplified asset pipeline instead of Sprockets. For new Rails 7+ projects, use `Propshaft` to handle asset digests and paths without the complexity of transpilation.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Gemfile
|
|
16
|
+
gem 'sprockets-rails'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct:**
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Gemfile
|
|
23
|
+
gem 'propshaft'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** Manual Review
|
|
27
|
+
---
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Structured Logging for Observability
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve log parsing and searching in production environments.
|
|
5
|
+
tags: ruby, rails, logs, observability, json
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Structured Logging for Observability
|
|
9
|
+
|
|
10
|
+
Improve log parsing and searching in production environments. Configure structured (JSON) logging in production to facilitate tracing and automated analysis.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Processing by UsersController#show as HTML
|
|
16
|
+
Parameters: {"id"=>"1"}
|
|
17
|
+
Completed 200 OK in 10ms
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"timestamp": "2025-02-05T12:00:00Z",
|
|
25
|
+
"level": "INFO",
|
|
26
|
+
"controller": "UsersController",
|
|
27
|
+
"action": "show",
|
|
28
|
+
"params": {"id": "1"},
|
|
29
|
+
"status": 200,
|
|
30
|
+
"duration_ms": 10
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Tools:** Lograge gem
|
|
35
|
+
---
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Prism as the default parser for tooling
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Leverage the faster, more accurate Ruby parser.
|
|
5
|
+
tags: ruby, tooling, prism, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Prism as the default parser for tooling
|
|
9
|
+
|
|
10
|
+
Leverage the faster, more accurate Ruby parser. Configure tools like `RuboCop` or `SyntaxTree` to use `Prism` for parsing where supported in Ruby 3.4+.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
# .rubocop.yml
|
|
16
|
+
AllCops:
|
|
17
|
+
ParserEngine: parser # Default
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
# .rubocop.yml
|
|
24
|
+
AllCops:
|
|
25
|
+
ParserEngine: prism
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** RuboCop (`ParserEngine: prism`)
|
|
29
|
+
---
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sử dụng observe property của Swift thay vì KVO cũ
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Tránh sử dụng cơ chế KVO lỗi thời, giảm thiểu độ phức tạp và lỗi khi override observeValue.
|
|
5
|
+
tags: swift, ios, kvo, observation, code-quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Sử dụng observe property của Swift thay vì KVO cũ
|
|
9
|
+
|
|
10
|
+
Ưu tiên dùng block-based KVO API với `keypaths` khi dùng Swift 3.2 trở lên. Loại bỏ việc override `observeValue` truyền thống vốn phức tạp và dễ sai.
|
|
11
|
+
|
|
12
|
+
**Incorrect (KVO cũ):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
class MyObserver: NSObject {
|
|
16
|
+
@objc var objectToObserve: MyObject
|
|
17
|
+
|
|
18
|
+
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
|
19
|
+
if keyPath == "someProperty" {
|
|
20
|
+
// Xử lý thay đổi
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (Block-based KVO):**
|
|
27
|
+
|
|
28
|
+
```swift
|
|
29
|
+
class MyObserver {
|
|
30
|
+
var observation: NSKeyValueObservation?
|
|
31
|
+
|
|
32
|
+
init(object: MyObject) {
|
|
33
|
+
observation = object.observe(\.someProperty, options: [.new]) { object, change in
|
|
34
|
+
// Xử lý thay đổi qua block
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** SwiftLint (block_based_kvo)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Delegate Protocol phải là class-only
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Cho phép dùng weak để tránh retain cycles (memory leak) giữa các đối tượng.
|
|
5
|
+
tags: swift, ios, delegate, memory-leak, AnyObject
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Delegate Protocol phải là class-only
|
|
9
|
+
|
|
10
|
+
`weak` chỉ hỗ trợ class types, do đó các delegate protocol phải được khai báo là `class`-based (`AnyObject`). Điều này giúp ngăn chặn retain cycles khi delegate trỏ ngược lại đối tượng sở hữu nó.
|
|
11
|
+
|
|
12
|
+
**Incorrect (không giới hạn class):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
protocol MyDelegate {
|
|
16
|
+
func didUpdateData()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class MyView {
|
|
20
|
+
var delegate: MyDelegate? // Lỗi: Không thể dùng weak ở đây nếu protocol không là AnyObject
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (class-only protocol):**
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
protocol MyDelegate: AnyObject {
|
|
28
|
+
func didUpdateData()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class MyView {
|
|
32
|
+
weak var delegate: MyDelegate? // Đúng: Có thể dùng weak để tránh memory leak
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** SwiftLint (class_delegate_protocol)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không khởi tạo trực tiếp các protocol hệ thống
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Tránh misuse các compiler protocol như ExpressibleByArrayLiteral, khuyến khích sử dụng literal syntax.
|
|
5
|
+
tags: swift, ios, compiler, literals, code-quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không khởi tạo trực tiếp các protocol hệ thống
|
|
9
|
+
|
|
10
|
+
Các protocol của compiler (như `ExpressibleByArrayLiteral`) không nên được gọi `init` trực tiếp. Hãy sử dụng cú pháp gọn gàng (literals) để khởi tạo giá trị.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Gọi init trực tiếp):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let array = Array(arrayLiteral: 1, 2, 3)
|
|
16
|
+
let dict = Dictionary(dictionaryLiteral: ("key", "value"))
|
|
17
|
+
let string = String(stringLiteral: "Hello")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (Sử dụng literal syntax):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let array = [1, 2, 3]
|
|
24
|
+
let dict = ["key": "value"]
|
|
25
|
+
let string = "Hello"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (compiler_protocol_init)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên sử dụng .contains trong một số phép lọc
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Cải thiện hiệu năng và độ rõ ràng của code bằng cách dùng phương thức chuyên dụng thay vì lọc rồi đếm.
|
|
5
|
+
tags: swift, ios, performance, collections, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên sử dụng .contains trong một số phép lọc
|
|
9
|
+
|
|
10
|
+
Thay vì `.filter { ... }.count > 0` hoặc `.first != nil`, hãy dùng `.contains`. `.contains` thường ngắn hơn, dễ hiểu hơn và hiệu quả hơn vì nó dừng ngay khi tìm thấy phần tử khớp.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Lọc rồi đếm):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let numbers = [1, 2, 3, 4, 5]
|
|
16
|
+
let hasEven = numbers.filter { $0 % 2 == 0 }.count > 0
|
|
17
|
+
let hasFive = numbers.first { $0 == 5 } != nil
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (Sử dụng .contains):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let numbers = [1, 2, 3, 4, 5]
|
|
24
|
+
let hasEven = numbers.contains { $0 % 2 == 0 }
|
|
25
|
+
let hasFive = numbers.contains(5)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (contains_over_*)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dùng enum cho type chỉ gồm static members
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Ngăn chặn việc khởi tạo các type không cần thiết khi chúng chỉ đóng vai trò là container cho hằng số hoặc tiện ích.
|
|
5
|
+
tags: swift, ios, architecture, enum, static-members
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Dùng enum cho type chỉ gồm static members
|
|
9
|
+
|
|
10
|
+
Nếu một type chỉ chứa static members (như hằng số, helper methods), nên dùng `enum` không có case để không cho phép khởi tạo. `enum` không thể được khởi tạo trực tiếp, giúp tránh việc lạm dụng hoặc tạo ra các instance vô nghĩa.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Sử dụng struct/class):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
struct Config {
|
|
16
|
+
static let apiKey = "SECRET"
|
|
17
|
+
static let baseUrl = "https://api.example.com"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let config = Config() // Vẫn có thể tạo instance
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (Sử dụng enum):**
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
enum Config {
|
|
27
|
+
static let apiKey = "SECRET"
|
|
28
|
+
static let baseUrl = "https://api.example.com"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// let config = Config() // Lỗi: Enum không có case không thể khởi tạo
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Tools:** SwiftLint (convenience_type)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Luôn dispose observer của NotificationCenter
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Tránh memory leak và các hành vi không mong muốn khi observer vẫn tồn tại dù đối tượng đã bị giải phóng.
|
|
5
|
+
tags: swift, ios, notification-center, memory-leak, observer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Luôn dispose observer của NotificationCenter
|
|
9
|
+
|
|
10
|
+
Luôn lưu lại token khi `addObserver` với NotificationCenter bằng block-based API. Gọi `removeObserver()` khi observer không còn cần thiết hoặc trong `deinit` để giải phóng bộ nhớ.
|
|
11
|
+
|
|
12
|
+
**Incorrect (không lưu token hoặc không remove):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
NotificationCenter.default.addObserver(forName: .myNotification, object: nil, queue: .main) { _ in
|
|
16
|
+
print("Notification received")
|
|
17
|
+
}
|
|
18
|
+
// Không có cách nào để remove observer này sau đó
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (lưu token và remove trong deinit):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
class MyManager {
|
|
25
|
+
private var observer: NSObjectProtocol?
|
|
26
|
+
|
|
27
|
+
init() {
|
|
28
|
+
observer = NotificationCenter.default.addObserver(forName: .myNotification, object: nil, queue: .main) { _ in
|
|
29
|
+
print("Notification received")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
deinit {
|
|
34
|
+
if let observer = observer {
|
|
35
|
+
NotificationCenter.default.removeObserver(observer)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** SwiftLint (discarded_notification_center_observer)
|