@sun-asterisk/sunlint 1.3.41 → 1.3.43
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/package.json +1 -1
- package/rules/security/S024_xpath_xxe_protection/typescript/regex-based-analyzer.js +4 -4
- package/rules/security/S024_xpath_xxe_protection/typescript/symbol-based-analyzer.js +1 -1
- package/rules/security/S025_server_side_validation/typescript/regex-based-analyzer.js +5 -5
- package/rules/security/S025_server_side_validation/typescript/symbol-based-analyzer.js +6 -6
- package/rules/security/S032_httponly_session_cookies/typescript/regex-based-analyzer.js +8 -8
- package/rules/security/S033_samesite_session_cookies/typescript/regex-based-analyzer.js +12 -12
- package/rules/security/S033_samesite_session_cookies/typescript/symbol-based-analyzer.js +1 -1
- package/rules/security/S034_host_prefix_session_cookies/typescript/regex-based-analyzer.js +1 -1
- package/rules/security/S041_session_token_invalidation/typescript/regex-based-analyzer.js +4 -4
- package/rules/security/S041_session_token_invalidation/typescript/symbol-based-analyzer.js +1 -1
- package/rules/security/S044_re_authentication_required/typescript/regex-based-analyzer.js +1 -1
- package/rules/security/S044_re_authentication_required/typescript/symbol-based-analyzer.js +1 -1
- package/rules/security/S045_brute_force_protection/typescript/analyzer.js +1 -1
- package/rules/security/S045_brute_force_protection/typescript/symbol-based-analyzer.js +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/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,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)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh khởi tạo trực tiếp các system types
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Tránh việc tạo các instance không hợp lệ hoặc không cần thiết của các system types có sẵn cơ chế factory/singleton.
|
|
5
|
+
tags: swift, ios, bundle, uidevice, initialization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh khởi tạo trực tiếp các system types
|
|
9
|
+
|
|
10
|
+
Tránh khởi tạo trực tiếp các kiểu như: `Bundle`, `NSError`, `UIDevice`. Sử dụng các factory method hoặc properties sẵn có (như `.main`, `.current`) thay vì gọi `init()` trực tiếp để đảm bảo tính nhất quán và hiệu năng.
|
|
11
|
+
|
|
12
|
+
**Incorrect (khởi tạo trực tiếp):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let bundle = Bundle()
|
|
16
|
+
let device = UIDevice()
|
|
17
|
+
let error = NSError()
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (sử dụng instance có sẵn):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let mainBundle = Bundle.main
|
|
24
|
+
let currentDevice = UIDevice.current
|
|
25
|
+
let designError = NSError(domain: "com.example", code: 404, userInfo: nil) // Cần truyền tham số nếu tạo mới hợp lệ
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (discouraged_direct_init)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không dùng optional cho Boolean
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Tránh ambiguity logic (3 trạng thái: true, false, nil) khi chỉ cần 2 trạng thái logic nhị phân.
|
|
5
|
+
tags: swift, ios, boolean, optional, logic
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không dùng optional cho Boolean
|
|
9
|
+
|
|
10
|
+
Không khai báo kiểu `Bool?` nếu có thể tránh được. Việc có 3 trạng thái (`true`, `false`, `nil`) cho một biến logic thường gây khó khăn trong việc kiểm soát luồng điều kiện và dễ dẫn đến lỗi logic. Hãy sử dụng giá trị mặc định rõ ràng.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dùng optional bool):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
var isExpanded: Bool?
|
|
16
|
+
|
|
17
|
+
if isExpanded == true {
|
|
18
|
+
// ...
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (dùng bool với giá trị mặc định):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
var isExpanded: Bool = false
|
|
26
|
+
|
|
27
|
+
if isExpanded {
|
|
28
|
+
// ...
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** SwiftLint (discouraged_optional_boolean)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên .isEmpty thay vì .count == 0
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Code rõ ràng hơn và hiệu năng tốt hơn (một số collection cần duyệt để đếm nhưng chỉ cần check phần tử đầu để biết có trống không).
|
|
5
|
+
tags: swift, ios, performance, collection, isEmpty
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên .isEmpty thay vì .count == 0
|
|
9
|
+
|
|
10
|
+
Dùng `.isEmpty` giúp code dễ đọc hơn và mang ý nghĩa rõ ràng hơn về mục đích kiểm tra tập hợp trống. Ngoài ra, `.count == 0` có thể chậm hơn trên một số loại collection so với `.isEmpty`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (kiểm tra count):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let items = [String]()
|
|
16
|
+
if items.count == 0 {
|
|
17
|
+
print("Danh sách trống")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (dùng isEmpty):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
let items = [String]()
|
|
25
|
+
if items.isEmpty {
|
|
26
|
+
print("Danh sách trống")
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (empty_count)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên isEmpty thay vì so sánh == ""
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Tăng độ rõ ràng và tránh lỗi tiềm ẩn khi xử lý chuỗi rỗng.
|
|
5
|
+
tags: swift, ios, string, isEmpty, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên isEmpty thay vì so sánh == ""
|
|
9
|
+
|
|
10
|
+
Dùng `.isEmpty` thay cho so sánh bằng chuỗi rỗng `""`. Điều này giúp code đồng bộ, chuyên nghiệp và rõ ràng hơn về mặt ngữ nghĩa trong Swift.
|
|
11
|
+
|
|
12
|
+
**Incorrect (so sánh chuỗi rỗng):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let name = ""
|
|
16
|
+
if name == "" {
|
|
17
|
+
print("Tên không được để trống")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (dùng isEmpty):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
let name = ""
|
|
25
|
+
if name.isEmpty {
|
|
26
|
+
print("Tên không được để trống")
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (empty_string)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không sử dụng .init() khi không cần thiết
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Giảm bớt sự rườm rà trong code, tuân thủ phong cách viết Swift ngắn gọn và hiện đại.
|
|
5
|
+
tags: swift, ios, initialization, init, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không sử dụng .init() khi không cần thiết
|
|
9
|
+
|
|
10
|
+
Tránh gọi `.init()` một cách tường minh nếu không bắt buộc. Trong Swift, việc khởi tạo đối tượng bằng cách gọi trực tiếp tên kiểu dữ liệu kèm tham số được ưu tiên hơn vì tính ngắn gọn và dễ đọc.
|
|
11
|
+
|
|
12
|
+
**Incorrect (gọi .init tường minh):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let point = CGPoint.init(x: 10, y: 20)
|
|
16
|
+
let user = User.init(name: "John")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (khởi tạo trực tiếp):**
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
let point = CGPoint(x: 10, y: 20)
|
|
23
|
+
let user = User(name: "John")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** SwiftLint (explicit_init)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Luôn phải có message rõ ràng khi sử dụng fatalError
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Giúp xác định nguyên nhân lỗi nhanh chóng khi ứng dụng bị crash trong quá trình phát triển hoặc test.
|
|
5
|
+
tags: swift, ios, error-handling, fatalError, debugging
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Luôn phải có message rõ ràng khi sử dụng fatalError
|
|
9
|
+
|
|
10
|
+
Bắt buộc thêm mô tả khi gọi `fatalError(...)`. Message này rất quan trọng để cung cấp ngữ cảnh về lý do tại sao code rơi vào trạng thái không thể hồi phục, giúp trace lỗi dễ dàng hơn trong log hoặc khi debug.
|
|
11
|
+
|
|
12
|
+
**Incorrect (không có message):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
func test() {
|
|
16
|
+
fatalError()
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (có message mô tả):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
func test() {
|
|
24
|
+
fatalError("Hàm này chưa được triển khai hoặc trạng thái không hợp lệ")
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (fatal_error_message)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên for-where thay vì if trong loop
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Code gọn gàng hơn, giảm bớt một tầng lồng nhau (nesting) và thể hiện rõ ý định lọc phần tử.
|
|
5
|
+
tags: swift, ios, loops, for-where, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên for-where thay vì if trong loop
|
|
9
|
+
|
|
10
|
+
Nếu chỉ có một điều kiện lọc duy nhất bên trong toàn bộ nội dung của vòng lặp, nên sử dụng cú pháp `for ... where` thay vì dùng câu lệnh `if` lồng bên trong. Điều này giúp mã nguồn phẳng hơn và làm rõ mục đích duyệt các phần tử thỏa mãn điều kiện.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dùng if lồng):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
for user in users {
|
|
16
|
+
if user.isActive {
|
|
17
|
+
print(user.name)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (dùng for-where):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
for user in users where user.isActive {
|
|
26
|
+
print(user.name)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (for_where)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh as! (force cast)
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Ngăn chặn crash ứng dụng do ép kiểu sai thực tế. Force-cast là nguyên nhân phổ biến gây lỗi runtime.
|
|
5
|
+
tags: swift, ios, casting, force-cast, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh as! (force cast)
|
|
9
|
+
|
|
10
|
+
Không sử dụng ép kiểu bắt buộc `as!`, thay vào đó nên dùng `as?` kết hợp với kiểm tra null (optional binding). Force-cast sẽ khiến ứng dụng bị crash ngay lập tức nếu đối tượng không thuộc kiểu dữ liệu mong đợi.
|
|
11
|
+
|
|
12
|
+
**Incorrect (force cast):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! MyCustomCell
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct (safe cast):**
|
|
19
|
+
|
|
20
|
+
```swift
|
|
21
|
+
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MyCustomCell else {
|
|
22
|
+
fatalError("Không thể cast cell sang MyCustomCell")
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** SwiftLint (force_cast)
|