@netoalmanca/advpl-sensei 1.1.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.
Files changed (70) hide show
  1. package/agents/changelog-generator.md +63 -0
  2. package/agents/code-generator.md +215 -0
  3. package/agents/code-reviewer.md +145 -0
  4. package/agents/debugger.md +83 -0
  5. package/agents/doc-generator.md +67 -0
  6. package/agents/docs-reference.md +86 -0
  7. package/agents/migrator.md +84 -0
  8. package/agents/process-consultant.md +97 -0
  9. package/agents/refactorer.md +75 -0
  10. package/agents/sx-configurator.md +67 -0
  11. package/commands/changelog.md +66 -0
  12. package/commands/diagnose.md +67 -0
  13. package/commands/docs.md +81 -0
  14. package/commands/document.md +67 -0
  15. package/commands/explain.md +60 -0
  16. package/commands/generate.md +111 -0
  17. package/commands/migrate.md +81 -0
  18. package/commands/process.md +111 -0
  19. package/commands/refactor.md +65 -0
  20. package/commands/review.md +60 -0
  21. package/commands/sxgen.md +98 -0
  22. package/commands/test.md +103 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +143 -0
  26. package/dist/index.js.map +1 -0
  27. package/package.json +30 -0
  28. package/skills/advpl-code-generation/SKILL.md +163 -0
  29. package/skills/advpl-code-generation/patterns-fwformbrowse.md +485 -0
  30. package/skills/advpl-code-generation/patterns-jobs.md +519 -0
  31. package/skills/advpl-code-generation/patterns-mvc.md +765 -0
  32. package/skills/advpl-code-generation/patterns-pontos-entrada.md +708 -0
  33. package/skills/advpl-code-generation/patterns-rest.md +974 -0
  34. package/skills/advpl-code-generation/patterns-soap.md +639 -0
  35. package/skills/advpl-code-generation/patterns-treport.md +481 -0
  36. package/skills/advpl-code-generation/patterns-workflow.md +779 -0
  37. package/skills/advpl-code-generation/templates-classes.md +1096 -0
  38. package/skills/advpl-code-review/SKILL.md +72 -0
  39. package/skills/advpl-code-review/rules-best-practices.md +444 -0
  40. package/skills/advpl-code-review/rules-modernization.md +290 -0
  41. package/skills/advpl-code-review/rules-performance.md +333 -0
  42. package/skills/advpl-code-review/rules-security.md +302 -0
  43. package/skills/advpl-debugging/SKILL.md +265 -0
  44. package/skills/advpl-debugging/common-errors.md +1124 -0
  45. package/skills/advpl-debugging/performance-tips.md +768 -0
  46. package/skills/advpl-refactoring/SKILL.md +139 -0
  47. package/skills/advpl-to-tlpp-migration/SKILL.md +293 -0
  48. package/skills/advpl-to-tlpp-migration/migration-checklist.md +122 -0
  49. package/skills/advpl-to-tlpp-migration/migration-rules.md +265 -0
  50. package/skills/changelog-patterns/SKILL.md +99 -0
  51. package/skills/code-explanation/SKILL.md +66 -0
  52. package/skills/documentation-patterns/SKILL.md +172 -0
  53. package/skills/embedded-sql/SKILL.md +379 -0
  54. package/skills/probat-testing/SKILL.md +226 -0
  55. package/skills/probat-testing/patterns-unit-tests.md +614 -0
  56. package/skills/protheus-business/SKILL.md +92 -0
  57. package/skills/protheus-business/modulo-compras.md +780 -0
  58. package/skills/protheus-business/modulo-contabilidade.md +874 -0
  59. package/skills/protheus-business/modulo-estoque.md +876 -0
  60. package/skills/protheus-business/modulo-faturamento.md +800 -0
  61. package/skills/protheus-business/modulo-financeiro.md +1015 -0
  62. package/skills/protheus-business/modulo-fiscal.md +749 -0
  63. package/skills/protheus-business/modulo-manutencao.md +848 -0
  64. package/skills/protheus-business/modulo-pcp.md +743 -0
  65. package/skills/protheus-reference/SKILL.md +119 -0
  66. package/skills/protheus-reference/native-functions.md +7029 -0
  67. package/skills/protheus-reference/rest-api-reference.md +1758 -0
  68. package/skills/protheus-reference/restricted-functions.md +265 -0
  69. package/skills/protheus-reference/sx-dictionary.md +854 -0
  70. package/skills/sx-configuration/SKILL.md +184 -0
@@ -0,0 +1,290 @@
1
+ # Modernization Rules
2
+
3
+ Rules for identifying modernization opportunities in ADVPL/TLPP code. Each rule includes a detection pattern, violation example, correct example, and explanation.
4
+
5
+ ---
6
+
7
+ ## [MOD-001] .prw file using class structures that could be migrated to .tlpp
8
+
9
+ **Severity:** INFO
10
+
11
+ **Description:** Classes defined in `.prw` files using `CLASS ... ENDCLASS` or `CLASS ... FROM` syntax are candidates for migration to `.tlpp`, which offers native OOP support with proper namespaces, annotations, and stronger encapsulation.
12
+
13
+ **What to look for:** `.prw` files containing `CLASS`, `DATA`, `METHOD`, `ENDCLASS` keywords defining object-oriented structures.
14
+
15
+ **Violation:**
16
+
17
+ ```advpl
18
+ // File: OrderService.prw
19
+ #Include "TOTVS.CH"
20
+
21
+ Class OrderService
22
+
23
+ Data cOrderId
24
+ Data nTotal
25
+ Data lProcessed
26
+
27
+ Method New() Constructor
28
+ Method Process()
29
+ Method Validate()
30
+
31
+ EndClass
32
+
33
+ Method New(cId) Class OrderService
34
+ ::cOrderId := cId
35
+ ::nTotal := 0
36
+ ::lProcessed := .F.
37
+ Return Self
38
+
39
+ Method Process() Class OrderService
40
+ // Processing logic
41
+ Return
42
+
43
+ Method Validate() Class OrderService
44
+ // Validation logic
45
+ Return .T.
46
+ ```
47
+
48
+ **Correct:**
49
+
50
+ ```tlpp
51
+ // File: OrderService.tlpp
52
+ #Include "tlpp-core.th"
53
+
54
+ Namespace custom.vendas
55
+
56
+ Class OrderService
57
+
58
+ Private Data cOrderId As Character
59
+ Private Data nTotal As Numeric
60
+ Private Data lProcessed As Logical
61
+
62
+ Public Method New(cId As Character) As Object
63
+ Public Method Process() As Logical
64
+ Public Method Validate() As Logical
65
+
66
+ EndClass
67
+
68
+ Method New(cId) Class OrderService
69
+ ::cOrderId := cId
70
+ ::nTotal := 0
71
+ ::lProcessed := .F.
72
+ Return Self
73
+
74
+ Method Process() Class OrderService
75
+ // Processing logic
76
+ Return .T.
77
+
78
+ Method Validate() Class OrderService
79
+ // Validation logic
80
+ Return .T.
81
+ ```
82
+
83
+ **Why it matters:** TLPP provides typed parameters, typed return values, access modifiers (`Public`, `Private`, `Protected`), proper namespaces, and annotations. These features improve code safety, IDE tooling, documentation, and alignment with modern development practices. Migrating classes to TLPP is the first step toward a more maintainable codebase.
84
+
85
+ ---
86
+
87
+ ## [MOD-002] using namespace directive instead of .th includes in TLPP files
88
+
89
+ **Severity:** INFO
90
+
91
+ **Description:** TLPP files should use `.th` include files (`tlpp-core.th`, `tlpp-rest.th`, `tlpp-object.th`, `tlpp-probat.th`) instead of `using namespace` directives. The `.th` includes are the officially supported mechanism and provide all necessary definitions.
92
+
93
+ **What to look for:** `using namespace tlpp.core`, `using namespace tlpp.rest`, `using namespace tlpp.log`, `using namespace tlpp.data`, or any `using namespace tlpp.*` directive in `.tlpp` files.
94
+
95
+ **Violation:**
96
+
97
+ ```tlpp
98
+ // File: ClientAPI.tlpp
99
+ using namespace tlpp.core
100
+ using namespace tlpp.rest
101
+ using namespace tlpp.log
102
+
103
+ Namespace custom.cadastro
104
+
105
+ Class ClientAPI
106
+
107
+ @Get("/api/v1/clients")
108
+ Public Method GetClients() As Logical
109
+
110
+ EndClass
111
+ ```
112
+
113
+ **Correct:**
114
+
115
+ ```tlpp
116
+ // File: ClientAPI.tlpp
117
+ #Include "tlpp-core.th"
118
+ #Include "tlpp-rest.th"
119
+
120
+ Namespace custom.cadastro
121
+
122
+ Class ClientAPI
123
+
124
+ @Get("/api/v1/clients")
125
+ Public Method GetClients() As Logical
126
+
127
+ EndClass
128
+ ```
129
+
130
+ **Why it matters:** The `using namespace` directive is not the recommended pattern for TLPP. The `.th` include files (`tlpp-core.th`, `tlpp-rest.th`, `tlpp-object.th`, `tlpp-probat.th`) provide all necessary type definitions, constants, and macros required by the TLPP runtime. Using `.th` includes ensures compatibility with current and future TOTVS releases.
131
+
132
+ ---
133
+
134
+ ## [MOD-003] Procedural functions with object-like patterns that should be class methods
135
+
136
+ **Severity:** INFO
137
+
138
+ **Description:** Groups of related functions that share a common prefix and pass context via parameters or private variables are candidates for refactoring into a class. This improves encapsulation, reusability, and testability.
139
+
140
+ **What to look for:** Multiple `User Function` or `Static Function` definitions with a common naming prefix (e.g., `Ped_Create`, `Ped_Validate`, `Ped_Save`, `Ped_Delete`) that operate on the same data set, often passing the same variables between them.
141
+
142
+ **Violation:**
143
+
144
+ ```advpl
145
+ // Procedural approach with shared prefix and repeated context passing
146
+ User Function Ped_Create(cCodCli, cCondPag)
147
+ Private aPedido := {}
148
+ Private cPedNum := GetSXENum("SC5", "C5_NUM")
149
+
150
+ aAdd(aPedido, {"C5_CLIENTE", cCodCli})
151
+ aAdd(aPedido, {"C5_CONDPAG", cCondPag})
152
+
153
+ If Ped_Validate(aPedido)
154
+ Ped_Save(aPedido)
155
+ EndIf
156
+
157
+ Return cPedNum
158
+
159
+ Static Function Ped_Validate(aPedido)
160
+ // Validation using the shared array
161
+ Return .T.
162
+
163
+ Static Function Ped_Save(aPedido)
164
+ // Save using the shared array
165
+ Return
166
+
167
+ Static Function Ped_Delete(cPedNum)
168
+ // Delete logic
169
+ Return
170
+ ```
171
+
172
+ **Correct:**
173
+
174
+ ```tlpp
175
+ // File: PedidoService.tlpp
176
+ #Include "tlpp-core.th"
177
+
178
+ Namespace custom.vendas
179
+
180
+ Class PedidoService
181
+
182
+ Private Data cCodCli As Character
183
+ Private Data cCondPag As Character
184
+ Private Data cPedNum As Character
185
+ Private Data aItems As Array
186
+
187
+ Public Method New(cCodCli As Character, cCondPag As Character) As Object
188
+ Public Method Validate() As Logical
189
+ Public Method Save() As Logical
190
+ Public Method Delete() As Logical
191
+
192
+ EndClass
193
+
194
+ Method New(cCodCli, cCondPag) Class PedidoService
195
+ ::cCodCli := cCodCli
196
+ ::cCondPag := cCondPag
197
+ ::cPedNum := GetSXENum("SC5", "C5_NUM")
198
+ ::aItems := {}
199
+ Return Self
200
+
201
+ Method Validate() Class PedidoService
202
+ // Validation using instance data - no parameter passing needed
203
+ Return .T.
204
+
205
+ Method Save() Class PedidoService
206
+ If ::Validate()
207
+ // Save using instance data
208
+ EndIf
209
+ Return .T.
210
+
211
+ Method Delete() Class PedidoService
212
+ // Delete using ::cPedNum
213
+ Return .T.
214
+ ```
215
+
216
+ **Why it matters:** Procedural code with shared prefixes is essentially an ad-hoc class without encapsulation. Converting to a proper class eliminates `Private` variable pollution, makes dependencies explicit, enables unit testing via mock objects, and improves code navigation in IDEs.
217
+
218
+ ---
219
+
220
+ ## [MOD-004] Legacy UI patterns that could use FWFormBrowse or MVC
221
+
222
+ **Severity:** INFO
223
+
224
+ **Description:** Legacy UI functions like `AxCadastro`, `Modelo 2` (`Ax2Model`), and `Modelo 3` (`Ax3Model`) are outdated patterns for building CRUD screens. Modern Protheus development uses `FWFormBrowse` or the MVC framework (`FWMVCModel`, `FWFormView`) for maintainable and standardized interfaces.
225
+
226
+ **What to look for:** Calls to `AxCadastro()`, `AxPesqui()`, `MBrowse()`, `Modelo2()`, `Modelo3()`, or manual `DEFINE MSDIALOG` / `ACTIVATE MSDIALOG` patterns for CRUD operations.
227
+
228
+ **Violation:**
229
+
230
+ ```advpl
231
+ // Legacy AxCadastro approach
232
+ User Function XPED001()
233
+ Local cAlias := "SC5"
234
+
235
+ Private cCadastro := "Pedidos de Venda"
236
+ Private aRotina := {}
237
+
238
+ aAdd(aRotina, {"Pesquisar", "AxPesqui()", 0, 1})
239
+ aAdd(aRotina, {"Incluir", "XPED001I()", 0, 3})
240
+ aAdd(aRotina, {"Alterar", "XPED001A()", 0, 4})
241
+ aAdd(aRotina, {"Excluir", "XPED001E()", 0, 5})
242
+ aAdd(aRotina, {"Visualizar","XPED001V()", 0, 2})
243
+
244
+ DbSelectArea(cAlias)
245
+ DbSetOrder(1)
246
+
247
+ AxCadastro(cAlias, cCadastro, aRotina)
248
+
249
+ Return
250
+ ```
251
+
252
+ **Correct:**
253
+
254
+ ```advpl
255
+ // MVC approach
256
+ #Include "TOTVS.CH"
257
+
258
+ User Function XPED001()
259
+
260
+ Local oBrowse := FWMBrowse():New()
261
+
262
+ oBrowse:SetAlias("SC5")
263
+ oBrowse:SetDescription("Pedidos de Venda")
264
+ oBrowse:Activate()
265
+
266
+ Return
267
+
268
+ Static Function ModelDef()
269
+ Local oModel := MPFormModel():New("XPED001M")
270
+ Local oStMaster := FWFormStruct(1, "SC5")
271
+
272
+ oModel:AddFields("SC5MASTER", /*cOwner*/, oStMaster)
273
+ oModel:SetPrimaryKey({"C5_FILIAL", "C5_NUM"})
274
+
275
+ Return oModel
276
+
277
+ Static Function ViewDef()
278
+ Local oView := FWFormView():New()
279
+ Local oModel := FWLoadModel("XPED001")
280
+ Local oStView := FWFormStruct(2, "SC5")
281
+
282
+ oView:SetModel(oModel)
283
+ oView:AddField("VIEW_SC5", oStView, "SC5MASTER")
284
+ oView:CreateHorizontalBox("SUPERIOR", 100)
285
+ oView:SetOwnerView("VIEW_SC5", "SUPERIOR")
286
+
287
+ Return oView
288
+ ```
289
+
290
+ **Why it matters:** Legacy UI patterns like `AxCadastro` do not support modern features such as field validation callbacks, automatic audit logging, REST API exposure, and standardized CRUD operations. The MVC framework provides a consistent architecture, reduces boilerplate code, and integrates with Protheus features like workflow approval and data auditing out of the box.
@@ -0,0 +1,333 @@
1
+ # Performance Rules
2
+
3
+ Rules for detecting performance issues in ADVPL/TLPP code. Each rule includes a detection pattern, violation example, correct example, and explanation.
4
+
5
+ ---
6
+
7
+ ## [PERF-001] SELECT * in Embedded SQL
8
+
9
+ **Severity:** CRITICAL
10
+
11
+ **Description:** Using `SELECT *` in Embedded SQL or TCQuery fetches all columns from the table, including large memo fields and unused columns. This wastes memory, increases network traffic, and slows down query execution.
12
+
13
+ **What to look for:** `SELECT *` or `SELECT ALIAS.*` inside `BeginSQL`/`EndSQL` blocks or in `TCQuery` strings.
14
+
15
+ **Violation:**
16
+
17
+ ```advpl
18
+ User Function GetOrders()
19
+ Local cAlias := GetNextAlias()
20
+
21
+ BeginSQL Alias cAlias
22
+ SELECT *
23
+ FROM %table:SC5% SC5
24
+ WHERE SC5.%notDel%
25
+ AND SC5.C5_FILIAL = %xfilial:SC5%
26
+ EndSQL
27
+
28
+ Return
29
+ ```
30
+
31
+ **Correct:**
32
+
33
+ ```advpl
34
+ User Function GetOrders()
35
+ Local cAlias := GetNextAlias()
36
+
37
+ BeginSQL Alias cAlias
38
+ SELECT SC5.C5_NUM, SC5.C5_CLIENTE, SC5.C5_LOJACLI,
39
+ SC5.C5_EMISSAO, SC5.C5_CONDPAG, SC5.C5_TOTPED
40
+ FROM %table:SC5% SC5
41
+ WHERE SC5.%notDel%
42
+ AND SC5.C5_FILIAL = %xfilial:SC5%
43
+ EndSQL
44
+
45
+ Return
46
+ ```
47
+
48
+ **Why it matters:** Protheus tables often have 100+ columns. Fetching all of them when only 5-6 are needed can multiply query time and memory usage by 10x or more. Specifying columns also makes the code self-documenting about which fields are actually used.
49
+
50
+ ---
51
+
52
+ ## [PERF-002] DbSeek inside loop when a single query could replace it
53
+
54
+ **Severity:** WARNING
55
+
56
+ **Description:** Performing `DbSeek` inside a loop (While, For) to look up data for each iteration causes N individual index lookups. A single SQL query with a JOIN or IN clause is significantly faster.
57
+
58
+ **What to look for:** `DbSeek` calls inside `While`/`EndDo`, `For`/`Next`, or similar loop constructs, especially when looking up supplementary data for records being processed.
59
+
60
+ **Violation:**
61
+
62
+ ```advpl
63
+ User Function ListOrderItems(cOrder)
64
+ Local aItems := {}
65
+
66
+ DbSelectArea("SC6")
67
+ DbSetOrder(1)
68
+ DbSeek(xFilial("SC6") + cOrder)
69
+
70
+ While !Eof() .And. SC6->C6_NUM == cOrder
71
+ // DbSeek inside loop for each item - N lookups!
72
+ DbSelectArea("SB1")
73
+ DbSetOrder(1)
74
+ If DbSeek(xFilial("SB1") + SC6->C6_PRODUTO)
75
+ aAdd(aItems, {SC6->C6_PRODUTO, SB1->B1_DESC, SC6->C6_QTDVEN})
76
+ EndIf
77
+
78
+ DbSelectArea("SC6")
79
+ DbSkip()
80
+ EndDo
81
+
82
+ Return aItems
83
+ ```
84
+
85
+ **Correct:**
86
+
87
+ ```advpl
88
+ User Function ListOrderItems(cOrder)
89
+ Local aItems := {}
90
+ Local cAlias := GetNextAlias()
91
+
92
+ BeginSQL Alias cAlias
93
+ SELECT SC6.C6_PRODUTO, SB1.B1_DESC, SC6.C6_QTDVEN
94
+ FROM %table:SC6% SC6
95
+ INNER JOIN %table:SB1% SB1
96
+ ON SB1.B1_FILIAL = %xfilial:SB1%
97
+ AND SB1.B1_COD = SC6.C6_PRODUTO
98
+ AND SB1.%notDel%
99
+ WHERE SC6.%notDel%
100
+ AND SC6.C6_FILIAL = %xfilial:SC6%
101
+ AND SC6.C6_NUM = %exp:cOrder%
102
+ EndSQL
103
+
104
+ DbSelectArea(cAlias)
105
+ While !(cAlias)->(Eof())
106
+ aAdd(aItems, {(cAlias)->C6_PRODUTO, (cAlias)->B1_DESC, (cAlias)->C6_QTDVEN})
107
+ (cAlias)->(DbSkip())
108
+ EndDo
109
+ (cAlias)->(DbCloseArea())
110
+
111
+ Return aItems
112
+ ```
113
+
114
+ **Why it matters:** Each `DbSeek` involves an index lookup and potential disk I/O. With 1,000 order items, that means 1,000 individual seeks to SB1. A single JOIN query lets the database engine optimize the access path and return all results in one round trip.
115
+
116
+ ---
117
+
118
+ ## [PERF-003] Missing TCSetField for column typing after query execution
119
+
120
+ **Severity:** WARNING
121
+
122
+ **Description:** After executing a query via `TCQuery` or `BeginSQL`, date and numeric columns are returned as character strings by default. Without `TCSetField` to define column types, every comparison or arithmetic operation requires manual type conversion, which is slower and error-prone.
123
+
124
+ **What to look for:** `TCQuery` or `BeginSQL` usage without subsequent `TCSetField` calls for date or numeric columns that are used in comparisons or calculations.
125
+
126
+ **Violation:**
127
+
128
+ ```advpl
129
+ User Function GetDueDate()
130
+ Local cAlias := GetNextAlias()
131
+ Local dDueDate
132
+
133
+ BeginSQL Alias cAlias
134
+ SELECT E2_VENCTO, E2_VALOR
135
+ FROM %table:SE2% SE2
136
+ WHERE SE2.%notDel%
137
+ AND SE2.E2_FILIAL = %xfilial:SE2%
138
+ EndSQL
139
+
140
+ // E2_VENCTO comes back as character "20250115"
141
+ // E2_VALOR comes back as character - arithmetic fails or gives wrong results
142
+ dDueDate := StoD((cAlias)->E2_VENCTO) // manual conversion needed everywhere
143
+
144
+ Return dDueDate
145
+ ```
146
+
147
+ **Correct:**
148
+
149
+ ```advpl
150
+ User Function GetDueDate()
151
+ Local cAlias := GetNextAlias()
152
+ Local dDueDate
153
+
154
+ BeginSQL Alias cAlias
155
+ SELECT E2_VENCTO, E2_VALOR
156
+ FROM %table:SE2% SE2
157
+ WHERE SE2.%notDel%
158
+ AND SE2.E2_FILIAL = %xfilial:SE2%
159
+ EndSQL
160
+
161
+ TCSetField(cAlias, "E2_VENCTO", "D")
162
+ TCSetField(cAlias, "E2_VALOR", "N", 14, 2)
163
+
164
+ // Now fields are properly typed
165
+ dDueDate := (cAlias)->E2_VENCTO // already a Date type
166
+
167
+ Return dDueDate
168
+ ```
169
+
170
+ **Why it matters:** Without `TCSetField`, developers must manually convert every field access with `StoD()`, `Val()`, etc. This adds overhead, produces verbose code, and introduces bugs when a conversion is forgotten. Setting field types once after the query is cleaner and faster.
171
+
172
+ ---
173
+
174
+ ## [PERF-004] String concatenation inside loop
175
+
176
+ **Severity:** WARNING
177
+
178
+ **Description:** Concatenating strings with `+` or `+=` inside a loop creates a new string object on every iteration. For large loops, this causes excessive memory allocation and garbage collection, severely degrading performance.
179
+
180
+ **What to look for:** String concatenation using `+` or `+=` inside `While`/`EndDo`, `For`/`Next` loops, especially when building large texts, CSV content, or log output.
181
+
182
+ **Violation:**
183
+
184
+ ```advpl
185
+ User Function BuildCSV()
186
+ Local cCSV := ""
187
+
188
+ DbSelectArea("SA1")
189
+ DbGoTop()
190
+
191
+ While !Eof()
192
+ // Creates a new string on every iteration
193
+ cCSV += SA1->A1_COD + ";" + SA1->A1_NOME + CRLF
194
+ DbSkip()
195
+ EndDo
196
+
197
+ Return cCSV
198
+ ```
199
+
200
+ **Correct:**
201
+
202
+ ```advpl
203
+ User Function BuildCSV()
204
+ Local aLines := {}
205
+ Local cCSV := ""
206
+
207
+ DbSelectArea("SA1")
208
+ DbGoTop()
209
+
210
+ While !Eof()
211
+ aAdd(aLines, SA1->A1_COD + ";" + SA1->A1_NOME)
212
+ DbSkip()
213
+ EndDo
214
+
215
+ cCSV := ArrTokStr(aLines, CRLF)
216
+
217
+ Return cCSV
218
+ ```
219
+
220
+ **Why it matters:** String concatenation in a loop has O(n^2) time complexity because each concatenation copies the entire accumulated string. With 10,000 records, this can take minutes instead of seconds. Collecting parts in an array and joining once at the end is O(n).
221
+
222
+ ---
223
+
224
+ ## [PERF-005] RecCount() to check if records exist
225
+
226
+ **Severity:** WARNING
227
+
228
+ **Description:** Using `RecCount()` to check whether a query returned results forces a full table/result scan to count all records. To check for existence, use `!Eof()` after a seek or query, which returns immediately.
229
+
230
+ **What to look for:** `RecCount()` used in conditions like `If RecCount() > 0`, `If RecCount() == 0`, or similar existence checks.
231
+
232
+ **Violation:**
233
+
234
+ ```advpl
235
+ User Function HasOrders(cCodCli)
236
+ Local lHas := .F.
237
+ Local cAlias := GetNextAlias()
238
+
239
+ BeginSQL Alias cAlias
240
+ SELECT C5_NUM
241
+ FROM %table:SC5% SC5
242
+ WHERE SC5.%notDel%
243
+ AND SC5.C5_FILIAL = %xfilial:SC5%
244
+ AND SC5.C5_CLIENTE = %exp:cCodCli%
245
+ EndSQL
246
+
247
+ // RecCount scans all matching records just to check existence
248
+ If (cAlias)->(RecCount()) > 0
249
+ lHas := .T.
250
+ EndIf
251
+
252
+ (cAlias)->(DbCloseArea())
253
+
254
+ Return lHas
255
+ ```
256
+
257
+ **Correct:**
258
+
259
+ ```advpl
260
+ User Function HasOrders(cCodCli)
261
+ Local lHas := .F.
262
+ Local cAlias := GetNextAlias()
263
+
264
+ BeginSQL Alias cAlias
265
+ SELECT TOP 1 C5_NUM
266
+ FROM %table:SC5% SC5
267
+ WHERE SC5.%notDel%
268
+ AND SC5.C5_FILIAL = %xfilial:SC5%
269
+ AND SC5.C5_CLIENTE = %exp:cCodCli%
270
+ EndSQL
271
+
272
+ // Eof() returns immediately - no need to count all records
273
+ lHas := !(cAlias)->(Eof())
274
+
275
+ (cAlias)->(DbCloseArea())
276
+
277
+ Return lHas
278
+ ```
279
+
280
+ **Why it matters:** `RecCount()` may trigger a full scan of the result set to count rows. If the query matches 50,000 records, all of them are counted just to check if any exist. `!Eof()` after a `SELECT TOP 1` stops at the first match, making existence checks virtually instant.
281
+
282
+ ---
283
+
284
+ ## [PERF-006] Query without proper index hint or ORDER BY matching existing index
285
+
286
+ **Severity:** INFO
287
+
288
+ **Description:** SQL queries that filter or sort by columns not covered by an existing index force full table scans. ORDER BY clauses should match the column order of an existing index for optimal performance.
289
+
290
+ **What to look for:** `WHERE` clauses filtering on columns that are not part of any index, or `ORDER BY` with column order that does not match any available index. Cross-reference with the SIX (index) table definition.
291
+
292
+ **Violation:**
293
+
294
+ ```advpl
295
+ User Function GetRecentOrders()
296
+ Local cAlias := GetNextAlias()
297
+
298
+ BeginSQL Alias cAlias
299
+ SELECT C5_NUM, C5_EMISSAO, C5_CLIENTE
300
+ FROM %table:SC5% SC5
301
+ WHERE SC5.%notDel%
302
+ AND SC5.C5_FILIAL = %xfilial:SC5%
303
+ ORDER BY C5_EMISSAO DESC
304
+ // C5_EMISSAO alone may not be an indexed column
305
+ // This could cause a full table sort
306
+ EndSQL
307
+
308
+ Return
309
+ ```
310
+
311
+ **Correct:**
312
+
313
+ ```advpl
314
+ User Function GetRecentOrders()
315
+ Local cAlias := GetNextAlias()
316
+
317
+ // Check SIX for SC5 indexes to find one that starts with C5_FILIAL + C5_EMISSAO
318
+ // If no suitable index exists, consider creating one or using a different approach
319
+
320
+ BeginSQL Alias cAlias
321
+ SELECT C5_NUM, C5_EMISSAO, C5_CLIENTE
322
+ FROM %table:SC5% SC5
323
+ WHERE SC5.%notDel%
324
+ AND SC5.C5_FILIAL = %xfilial:SC5%
325
+ AND SC5.C5_EMISSAO >= %exp:DtoS(dDataIni)%
326
+ ORDER BY C5_FILIAL, C5_EMISSAO DESC
327
+ // ORDER BY matches index column order for better performance
328
+ EndSQL
329
+
330
+ Return
331
+ ```
332
+
333
+ **Why it matters:** Queries without proper index support perform full table scans, which grow linearly with table size. A table with 1 million records can take minutes to scan versus milliseconds with a proper index seek. Always verify that your WHERE and ORDER BY clauses align with the available indexes in the SIX table.