@mocoto/mahoraga 0.14.2 → 0.14.3

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 (105) hide show
  1. package/README.md +0 -34
  2. package/dist/analysts/architects/analyst-structure.js +19 -19
  3. package/dist/analysts/architects/archetypes-custom.js +8 -6
  4. package/dist/analysts/architects/detector-archetypes.js +30 -27
  5. package/dist/analysts/architects/signals-project-advanced.js +24 -26
  6. package/dist/analysts/architects/signals-project.js +1 -1
  7. package/dist/analysts/architects/strategist-structure.js +47 -66
  8. package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +21 -14
  9. package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
  10. package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
  11. package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
  12. package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -37
  13. package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
  14. package/dist/analysts/circleci/analysts/analyst-circleci.js +23 -15
  15. package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
  16. package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
  17. package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
  18. package/dist/analysts/circleci/detectors/detector-circleci-structure.js +20 -17
  19. package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
  20. package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
  21. package/dist/analysts/corrections/analyst-scoring.js +24 -21
  22. package/dist/analysts/corrections/corrector-structure.js +35 -35
  23. package/dist/analysts/corrections/fix-alias-imports.js +10 -6
  24. package/dist/analysts/corrections/fix-md-fences.js +23 -23
  25. package/dist/analysts/corrections/map-reversion.js +54 -51
  26. package/dist/analysts/corrections/pruning.js +19 -16
  27. package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
  28. package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
  29. package/dist/analysts/corrections/reorganize-barrels.js +10 -10
  30. package/dist/analysts/corrections/scoring-shared.js +19 -18
  31. package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
  32. package/dist/analysts/corrections/type-safety/type-validator.js +2 -1
  33. package/dist/analysts/corrections/type-safety/usage-analyzer.js +36 -39
  34. package/dist/analysts/css/analysts/analyst-css.js +16 -15
  35. package/dist/analysts/css/corrections/correction-css.js +19 -11
  36. package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
  37. package/dist/analysts/css/scorers/scorer-css.js +23 -18
  38. package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
  39. package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
  40. package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +20 -15
  41. package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
  42. package/dist/analysts/css-in-js/shared.js +7 -0
  43. package/dist/analysts/detectors/detector-architecture.js +89 -83
  44. package/dist/analysts/detectors/detector-bugs-ml.js +21 -19
  45. package/dist/analysts/detectors/detector-code-fragile.js +30 -26
  46. package/dist/analysts/detectors/detector-duplications.js +21 -18
  47. package/dist/analysts/detectors/detector-monorepo.js +41 -36
  48. package/dist/analysts/detectors/detector-performance.js +22 -16
  49. package/dist/analysts/detectors/detector-recommendation-architecture.js +39 -35
  50. package/dist/analysts/detectors/detector-security.js +43 -42
  51. package/dist/analysts/detectors/detector-structure.js +29 -26
  52. package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +4 -1
  53. package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +10 -16
  54. package/dist/analysts/plugins/detector-documentation.js +1 -0
  55. package/dist/analysts/react/analysts/analyst-react-hooks.js +4 -2
  56. package/dist/analysts/react/analysts/analyst-react.js +1 -1
  57. package/dist/analysts/react/corrections/correction-react.js +2 -2
  58. package/dist/analysts/react/detectors/detector-react-best-practices.js +1 -1
  59. package/dist/analysts/registry/autodiscovery.js +2 -2
  60. package/dist/analysts/tailwind/corrections/correction-tailwind.js +0 -8
  61. package/dist/analysts/tailwind/scorers/scorer-tailwind.js +1 -2
  62. package/dist/cli/commands/command-analysts.js +3 -2
  63. package/dist/cli/commands/command-formatters.js +1 -1
  64. package/dist/cli/commands/command-github-actions.js +1 -1
  65. package/dist/cli/commands/command-guardian.js +1 -1
  66. package/dist/cli/commands/command-marketplace.js +4 -4
  67. package/dist/cli/diagnostic/filters.js +9 -9
  68. package/dist/core/config/chalk-safe.js +4 -4
  69. package/dist/core/config/config.js +5 -5
  70. package/dist/core/execution/analysis-cache.js +1 -1
  71. package/dist/core/execution/ast-cache.js +1 -3
  72. package/dist/core/execution/executor.js +2 -4
  73. package/dist/core/execution/scanner.js +1 -1
  74. package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
  75. package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
  76. package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
  77. package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
  78. package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
  79. package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
  80. package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
  81. package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
  82. package/dist/core/parsing/parser.js +2 -3
  83. package/dist/core/parsing/utils.js +1 -0
  84. package/dist/core/workers/worker-pool.js +0 -0
  85. package/dist/guardian/records.js +1 -0
  86. package/dist/guardian/sentinel.js +1 -0
  87. package/dist/guardian/watcher-hidden.js +1 -0
  88. package/dist/licenses/fs-utils.js +2 -1
  89. package/dist/licenses/header-options.js +1 -0
  90. package/dist/licenses/index.js +1 -1
  91. package/dist/reports/generator-report.js +3 -3
  92. package/dist/reports/report-patterns-usage.js +1 -1
  93. package/dist/shared/data-processing/fragment-report.js +1 -1
  94. package/dist/shared/formatters/engines/stylelint.js +40 -34
  95. package/dist/shared/formatters/formatters/markdown.js +1 -1
  96. package/dist/shared/helpers/framework-detector.js +5 -1
  97. package/dist/shared/persistence/persistence.js +13 -5
  98. package/dist/shared/plugins/core-plugin.js +3 -1
  99. package/dist/types/analysts/tailwind-correction.js +2 -0
  100. package/dist/types/index.js +3 -3
  101. package/dist/types/processing/filters.js +5 -4
  102. package/dist/vulnerabilities/npm-audit.js +1 -0
  103. package/dist/vulnerabilities/scanner.js +1 -1
  104. package/dist/vulnerabilities/vulnerabilities.js +1 -0
  105. package/package.json +5 -10
@@ -236,6 +236,24 @@ export function fixSpacingAroundPunct(string) {
236
236
  count
237
237
  };
238
238
  }
239
+ function getCharacterDisplayName(ch) {
240
+ try {
241
+ if (typeof Intl !== 'undefined') {
242
+ const intlApi = Intl;
243
+ const DisplayNomesCtor = intlApi.DisplayNames;
244
+ if (DisplayNomesCtor) {
245
+ const displayNomes = new DisplayNomesCtor(['en'], {
246
+ type: 'language'
247
+ });
248
+ return typeof displayNomes.of === 'function' ? displayNomes.of(ch) ?? '' : '';
249
+ }
250
+ }
251
+ return '';
252
+ }
253
+ catch {
254
+ return '';
255
+ }
256
+ }
239
257
  /**
240
258
  * Detecta caracteres fora do range ASCII comum.
241
259
  */
@@ -248,24 +266,7 @@ export function detectUncommonChars(text, limite) {
248
266
  }
249
267
  const code = ch.codePointAt(0) ?? 0;
250
268
  if (code >= LIMITES.ASCII_EXTENDED_MIN && !isCaractereComum(code)) {
251
- const name = (() => {
252
- try {
253
- if (typeof Intl !== 'undefined') {
254
- const intlApi = Intl;
255
- const DisplayNomesCtor = intlApi.DisplayNames;
256
- if (DisplayNomesCtor) {
257
- const displayNomes = new DisplayNomesCtor(['en'], {
258
- type: 'language'
259
- });
260
- return typeof displayNomes.of === 'function' ? displayNomes.of(ch) ?? '' : '';
261
- }
262
- }
263
- return '';
264
- }
265
- catch {
266
- return '';
267
- }
268
- })();
269
+ const name = getCharacterDisplayName(ch);
269
270
  issues.push({
270
271
  tipo: 'caracteres-incomuns',
271
272
  posicao: pos,
@@ -5,6 +5,7 @@
5
5
  * Analisador de contexto para Type Safety Auto-Fix
6
6
  * Detecta contextos onde any/unknown NÃO devem ser modificados
7
7
  */
8
+ const CONFIANCA_PADRAO = 60;
8
9
  export { isInComment, isInString, isInStringOrComment } from '../../../shared/helpers/index.js';
9
10
  /**
10
11
  * Verifica se está em contexto TypeScript específico que deve ser ignorado
@@ -152,6 +153,13 @@ export function extractLineContext(code, position) {
152
153
  }
153
154
  return '';
154
155
  }
156
+ function hasInternalValidation(code, lineContext) {
157
+ if (!/(analise|result|data|value)\s*:\s*unknown/.test(lineContext)) {
158
+ return false;
159
+ }
160
+ const beforeFunc = code.substring(Math.max(0, lineContext.length - 200), lineContext.length + 100);
161
+ return /if\s*\(/.test(beforeFunc) || /typeof\s+\w+\s*===/.test(beforeFunc);
162
+ }
155
163
  export function categorizarUnknown(code, fileCaminho, lineContext) {
156
164
  // Type Guards - 100% legítimo
157
165
  if (/:\s*unknown\)\s*:\s*\w+\s+is\s+/.test(lineContext)) {
@@ -393,15 +401,12 @@ export function categorizarUnknown(code, fileCaminho, lineContext) {
393
401
  };
394
402
  }
395
403
  // Função de análise com validação interna - 90% legítimo
396
- if (/(analise|result|data|value)\s*:\s*unknown/.test(lineContext)) {
397
- const beforeFunc = code.substring(Math.max(0, lineContext.length - 200), lineContext.length + 100);
398
- if (/if\s*\(/.test(beforeFunc) || /typeof\s+\w+\s*===/.test(beforeFunc)) {
399
- return {
400
- categoria: 'legitimo',
401
- confianca: 90,
402
- motivo: 'Parâmetro com validação/narrowing interna - unknown é seguro quando verificado'
403
- };
404
- }
404
+ if (hasInternalValidation(code, lineContext)) {
405
+ return {
406
+ categoria: 'legitimo',
407
+ confianca: 90,
408
+ motivo: 'Parâmetro com validação/narrowing interna - unknown é seguro quando verificado'
409
+ };
405
410
  }
406
411
  // Getter de keys com validação - 95% legítimo
407
412
  if (/value\s*:\s*unknown/.test(lineContext) && /Object\.keys/.test(contextoAmplo)) {
@@ -422,7 +427,7 @@ export function categorizarUnknown(code, fileCaminho, lineContext) {
422
427
  // Default: melhorável com análise incerta
423
428
  return {
424
429
  categoria: 'melhoravel',
425
- confianca: 60,
430
+ confianca: CONFIANCA_PADRAO,
426
431
  motivo: 'Tipo unknown genérico - análise contextual limitada',
427
432
  sugestao: 'Analisar fluxo de dados para inferir tipo correto',
428
433
  variantes: ['Se vem de API externa: definir interface baseada na resposta esperada', 'Se é callback: especificar assinatura da função', 'Se é config/options: criar interface com campos opcionais', 'Se é polimórfico: considerar discriminated union ou generics']
@@ -4,6 +4,7 @@
4
4
  * Verifica compatibilidade e compilação TypeScript
5
5
  */
6
6
  import { findExistingType, isSameType } from './type-creator.js';
7
+ const CONFIANCA_MINIMA = 60;
7
8
  /**
8
9
  * Valida substituição de tipo (any/unknown → specific)
9
10
  */
@@ -36,7 +37,7 @@ export async function validateTypeReplacement(originalCode, fixedCodigo, typeAna
36
37
  }
37
38
  }
38
39
  // 4. Verificar confiança mínima
39
- if (typeAnalise.confidence < 60) {
40
+ if (typeAnalise.confidence < CONFIANCA_MINIMA) {
40
41
  result.warnings.push(`Confiança muito baixa (${typeAnalise.confidence}%). Considere revisão manual.`);
41
42
  }
42
43
  // 5. Validar sintaxe básica do código modificado
@@ -128,49 +128,46 @@ function extractObjectProperties(usages) {
128
128
  }
129
129
  return Array.from(propertiesMap.values());
130
130
  }
131
- /**
132
- * Detecta type guards no código
133
- */
131
+ function detectTypeofGuard(usage, context, guards) {
132
+ const match = context.match(/typeof\s+\w+\s*===\s*['"](\w+)['"]/);
133
+ if (match) {
134
+ guards.push({
135
+ type: 'typeof',
136
+ expression: usage.context,
137
+ inferredTipo: match[1],
138
+ confidence: 95
139
+ });
140
+ }
141
+ }
142
+ function detectInstanceofGuard(usage, context, guards) {
143
+ const match = context.match(/\w+\s+instanceof\s+(\w+)/);
144
+ if (match) {
145
+ guards.push({
146
+ type: 'instanceof',
147
+ expression: usage.context,
148
+ inferredTipo: match[1],
149
+ confidence: 95
150
+ });
151
+ }
152
+ }
153
+ function detectInGuard(usage, context, guards) {
154
+ const match = context.match(/['"](\w+)['"]\s+in\s+\w+/);
155
+ if (match) {
156
+ guards.push({
157
+ type: 'in',
158
+ expression: usage.context,
159
+ inferredTipo: `{ ${match[1]}: unknown }`,
160
+ confidence: 80
161
+ });
162
+ }
163
+ }
134
164
  function detectTypeGuards(usages) {
135
165
  const guards = [];
136
166
  for (const usage of usages) {
137
167
  const context = usage.context.toLowerCase();
138
- // typeof guards
139
- if (context.includes('typeof')) {
140
- const match = context.match(/typeof\s+\w+\s*===\s*['"](\w+)['"]/);
141
- if (match) {
142
- guards.push({
143
- type: 'typeof',
144
- expression: usage.context,
145
- inferredTipo: match[1],
146
- confidence: 95
147
- });
148
- }
149
- }
150
- // instanceof guards
151
- if (context.includes('instanceof')) {
152
- const match = context.match(/\w+\s+instanceof\s+(\w+)/);
153
- if (match) {
154
- guards.push({
155
- type: 'instanceof',
156
- expression: usage.context,
157
- inferredTipo: match[1],
158
- confidence: 95
159
- });
160
- }
161
- }
162
- // 'in' operator guards
163
- if (context.includes(' in ')) {
164
- const match = context.match(/['"](\w+)['"]\s+in\s+\w+/);
165
- if (match) {
166
- guards.push({
167
- type: 'in',
168
- expression: usage.context,
169
- inferredTipo: `{ ${match[1]}: unknown }`,
170
- confidence: 80
171
- });
172
- }
173
- }
168
+ detectTypeofGuard(usage, context, guards);
169
+ detectInstanceofGuard(usage, context, guards);
170
+ detectInGuard(usage, context, guards);
174
171
  }
175
172
  return guards;
176
173
  }
@@ -213,6 +213,18 @@ function detectCssHacks(value) {
213
213
  }
214
214
  return null;
215
215
  }
216
+ function processDuplicateProperty(propChave, propRaw, prev, value, inFontFace, relPath, line, pushOnce) {
217
+ const ctx = inFontFace ? { currentAtRule: 'font-face' } : undefined;
218
+ if (isLikelyIntentionalDuplicate(propChave, prev.value, value, ctx)) {
219
+ return;
220
+ }
221
+ if (prev.value === value) {
222
+ pushOnce(warn(messages.CssMensagens.duplicatePropertySame(propRaw), relPath, line));
223
+ }
224
+ else {
225
+ pushOnce(warn(messages.CssMensagens.duplicatePropertyDifferent(propRaw, prev.value, value), relPath, line));
226
+ }
227
+ }
216
228
  function collectCssIssuesFromPostCssAst(root, relPath) {
217
229
  const ocorrencias = [];
218
230
  const seen = new Set();
@@ -242,18 +254,7 @@ function collectCssIssuesFromPostCssAst(root, relPath) {
242
254
  const line = getNodeLine(decl);
243
255
  const prev = props[propChave];
244
256
  if (propChave in props) {
245
- const ctx = inFontFace ? {
246
- currentAtRule: 'font-face'
247
- } : undefined;
248
- if (isLikelyIntentionalDuplicate(propChave, prev.value, value, ctx)) {
249
- // duplicata intencional
250
- }
251
- else if (prev.value === value) {
252
- pushOnce(warn(messages.CssMensagens.duplicatePropertySame(propRaw), relPath, line));
253
- }
254
- else {
255
- pushOnce(warn(messages.CssMensagens.duplicatePropertyDifferent(propRaw, prev.value, value), relPath, line));
256
- }
257
+ processDuplicateProperty(propChave, propRaw, prev, value, inFontFace, relPath, line, pushOnce);
257
258
  }
258
259
  props[propChave] = {
259
260
  value,
@@ -279,11 +280,11 @@ function collectCssIssuesFromPostCssAst(root, relPath) {
279
280
  // 2) Unificação/centralização (apenas em regras com seletores simples)
280
281
  if (container.type === 'rule') {
281
282
  const rule = container;
282
- const selectorText = (rule.selector || '');
283
+ const selectorText = rule.selector;
283
284
  const simpleSelectors = splitSimpleSelectors(selectorText);
284
285
  if (simpleSelectors.length) {
285
286
  const decls = [];
286
- rule.nodes.forEach(node => {
287
+ rule.nodes?.forEach(node => {
287
288
  if (node.type !== 'decl') {
288
289
  return;
289
290
  }
@@ -299,7 +300,7 @@ function collectCssIssuesFromPostCssAst(root, relPath) {
299
300
  });
300
301
  });
301
302
  // Detecta regras vazias (ignora @apply do Tailwind CSS)
302
- const hasApplyDirective = rule.nodes.some(node => node.type === 'atrule' && (node).name === 'apply');
303
+ const hasApplyDirective = rule.nodes?.some(node => node.type === 'atrule' && (node).name === 'apply');
303
304
  if (decls.length === 0 && !hasApplyDirective) {
304
305
  pushOnce(warn(messages.CssMensagens.emptyRule, relPath, getNodeLine(rule)));
305
306
  }
@@ -10,6 +10,22 @@
10
10
  import { messages } from '../../../core/messages/index.js';
11
11
  import { splitLines } from '../../../shared/helpers/index.js';
12
12
  import { criarOcorrencia } from '../../../types/index.js';
13
+ function detectarIdSelector(linha, numeroLinha) {
14
+ if (!/#[a-zA-Z0-9_-]+\s*\{/.test(linha)) {
15
+ return null;
16
+ }
17
+ const idMatch = linha.match(/#([a-zA-Z0-9_-]+)/);
18
+ if (!idMatch) {
19
+ return null;
20
+ }
21
+ return {
22
+ tipo: 'css-id-to-class',
23
+ original: linha.trim(),
24
+ sugerido: linha.replace(/#([a-zA-Z0-9_-]+)/, `.${idMatch[1]}`),
25
+ linha: numeroLinha,
26
+ confianca: 70
27
+ };
28
+ }
13
29
  export function detectarCorrecoesCss(src, _relPath) {
14
30
  const correcoes = [];
15
31
  const linhas = splitLines(src);
@@ -24,17 +40,9 @@ export function detectarCorrecoesCss(src, _relPath) {
24
40
  confianca: 80
25
41
  });
26
42
  }
27
- if (/#[a-zA-Z0-9_-]+\s*\{/.test(linha)) {
28
- const idMatch = linha.match(/#([a-zA-Z0-9_-]+)/);
29
- if (idMatch) {
30
- correcoes.push({
31
- tipo: 'css-id-to-class',
32
- original: linha.trim(),
33
- sugerido: linha.replace(/#([a-zA-Z0-9_-]+)/, `.${idMatch[1]}`),
34
- linha: numeroLinha,
35
- confianca: 70
36
- });
37
- }
43
+ const idCorrection = detectarIdSelector(linha, numeroLinha);
44
+ if (idCorrection) {
45
+ correcoes.push(idCorrection);
38
46
  }
39
47
  });
40
48
  return correcoes;
@@ -63,151 +63,162 @@ export const detectorCssBestPractices = {
63
63
  const _idSelectorCount = 0;
64
64
  const allSelectors = [];
65
65
  const _selectorDepth = 0;
66
- linhas.forEach((linha, index) => {
67
- const numeroLinha = index + 1;
68
- const trimmed = linha.trim();
69
- if (trimmed.includes('{')) {
70
- depth++;
71
- if (depth > maxDepth) {
72
- maxDepth = depth;
66
+ function processarAberturaBloco(linha, trimmed, numeroLinha) {
67
+ depth++;
68
+ if (depth > maxDepth) {
69
+ maxDepth = depth;
70
+ }
71
+ const selectorPart = trimmed.replace(/\s*\{.*$/, '').trim();
72
+ if (!selectorPart) {
73
+ return;
74
+ }
75
+ allSelectors.push(selectorPart);
76
+ const simpleCount = selectorPart.split(',').length;
77
+ if (simpleCount > 5) {
78
+ ocorrencias.push(criarOcorrencia({
79
+ tipo: 'css-too-many-selectors',
80
+ nivel: 'info',
81
+ mensagem: messages.DetectorCssMensagens.tooManySelectors,
82
+ relPath,
83
+ linha: numeroLinha,
84
+ sugestao: messages.DetectorCssMensagens.tooManySelectorsSugestao
85
+ }));
86
+ }
87
+ const baseSelector = selectorPart.replace(/,.*$/, '').trim();
88
+ if (baseSelector) {
89
+ if (seenSelectors.has(baseSelector)) {
90
+ seenSelectors.get(baseSelector)?.push(numeroLinha);
73
91
  }
74
- const selectorPart = trimmed.replace(/\s*\{.*$/, '').trim();
75
- if (selectorPart) {
76
- allSelectors.push(selectorPart);
77
- const simpleCount = selectorPart.split(',').length;
78
- if (simpleCount > 5) {
79
- ocorrencias.push(criarOcorrencia({
80
- tipo: 'css-too-many-selectors',
81
- nivel: 'info',
82
- mensagem: messages.DetectorCssMensagens.tooManySelectors,
83
- relPath,
84
- linha: numeroLinha,
85
- sugestao: messages.DetectorCssMensagens.tooManySelectorsSugestao
86
- }));
87
- }
88
- const baseSelector = selectorPart.replace(/,.*$/, '').trim();
89
- if (baseSelector) {
90
- if (seenSelectors.has(baseSelector)) {
91
- seenSelectors.get(baseSelector)?.push(numeroLinha);
92
- }
93
- else {
94
- seenSelectors.set(baseSelector, [numeroLinha]);
95
- }
96
- }
92
+ else {
93
+ seenSelectors.set(baseSelector, [numeroLinha]);
97
94
  }
98
95
  }
99
- if (trimmed.includes('}')) {
100
- if (propsInBlock.size === 0 && depth > 0) {
101
- ocorrencias.push(criarOcorrencia({
102
- tipo: 'css-empty-rule',
103
- nivel: 'info',
104
- mensagem: messages.DetectorCssMensagens.emptyRule,
105
- relPath,
106
- linha: numeroLinha,
107
- sugestao: messages.DetectorCssMensagens.emptyRuleSugestao
108
- }));
109
- }
110
- propsInBlock.clear();
111
- depth--;
96
+ }
97
+ function processarFechamentoBloco(numeroLinha) {
98
+ if (propsInBlock.size === 0 && depth > 0) {
99
+ ocorrencias.push(criarOcorrencia({
100
+ tipo: 'css-empty-rule',
101
+ nivel: 'info',
102
+ mensagem: messages.DetectorCssMensagens.emptyRule,
103
+ relPath,
104
+ linha: numeroLinha,
105
+ sugestao: messages.DetectorCssMensagens.emptyRuleSugestao
106
+ }));
112
107
  }
113
- if (/!important/.test(trimmed)) {
108
+ propsInBlock.clear();
109
+ depth--;
110
+ }
111
+ function processarPropriedade(trimmed, numeroLinha) {
112
+ const propMatch = /^([a-zA-Z-]+)\s*:\s*([^;]+)?/.exec(trimmed);
113
+ if (!propMatch || depth <= 0) {
114
+ return;
115
+ }
116
+ const prop = propMatch[1].toLowerCase();
117
+ const value = (propMatch[2] || '').trim();
118
+ allDeclaredProps.push(prop);
119
+ if (propsInBlock.has(prop)) {
114
120
  ocorrencias.push(criarOcorrencia({
115
- tipo: 'css-important-usage',
121
+ tipo: 'css-duplicate-property',
116
122
  nivel: 'aviso',
117
- mensagem: messages.DetectorCssMensagens.important,
123
+ mensagem: messages.DetectorCssMensagens.duplicateProperty(prop),
118
124
  relPath,
119
125
  linha: numeroLinha,
120
- sugestao: messages.DetectorCssMensagens.importantSugestao
126
+ sugestao: messages.DetectorCssMensagens.duplicatePropertySugestao
121
127
  }));
122
128
  }
123
- const propMatch = /^([a-zA-Z-]+)\s*:\s*([^;]+)?/.exec(trimmed);
124
- if (propMatch && depth > 0) {
125
- const prop = propMatch[1].toLowerCase();
126
- const value = (propMatch[2] || '').trim();
127
- allDeclaredProps.push(prop);
128
- if (propsInBlock.has(prop)) {
129
- ocorrencias.push(criarOcorrencia({
130
- tipo: 'css-duplicate-property',
131
- nivel: 'aviso',
132
- mensagem: messages.DetectorCssMensagens.duplicateProperty(prop),
133
- relPath,
134
- linha: numeroLinha,
135
- sugestao: messages.DetectorCssMensagens.duplicatePropertySugestao
136
- }));
137
- }
138
- propsInBlock.set(prop, { line: numeroLinha, value });
139
- if (value === '0' && !prop.startsWith('--')) {
129
+ propsInBlock.set(prop, { line: numeroLinha, value });
130
+ if (value === '0' && !prop.startsWith('--')) {
131
+ ocorrencias.push(criarOcorrencia({
132
+ tipo: 'css-unitless-zero',
133
+ nivel: 'info',
134
+ mensagem: messages.DetectorCssMensagens.unitlessZero,
135
+ relPath,
136
+ linha: numeroLinha,
137
+ sugestao: messages.DetectorCssMensagens.unitlessZeroSugestao
138
+ }));
139
+ }
140
+ if (prop === 'font-family') {
141
+ const families = value.split(',').map(f => f.trim().replace(/['"]/g, ''));
142
+ const hasGeneric = families.some(f => GENERIC_FONTS.has(f.toLowerCase()));
143
+ if (!hasGeneric) {
140
144
  ocorrencias.push(criarOcorrencia({
141
- tipo: 'css-unitless-zero',
145
+ tipo: 'css-font-stack-missing',
142
146
  nivel: 'info',
143
- mensagem: messages.DetectorCssMensagens.unitlessZero,
147
+ mensagem: messages.DetectorCssMensagens.fontStackMissing,
144
148
  relPath,
145
149
  linha: numeroLinha,
146
- sugestao: messages.DetectorCssMensagens.unitlessZeroSugestao
150
+ sugestao: messages.DetectorCssMensagens.fontStackMissingSugestao
147
151
  }));
148
152
  }
149
- if (prop === 'font-family') {
150
- const families = value.split(',').map(f => f.trim().replace(/['"]/g, ''));
151
- const hasGeneric = families.some(f => GENERIC_FONTS.has(f.toLowerCase()));
152
- if (!hasGeneric) {
153
- ocorrencias.push(criarOcorrencia({
154
- tipo: 'css-font-stack-missing',
155
- nivel: 'info',
156
- mensagem: messages.DetectorCssMensagens.fontStackMissing,
157
- relPath,
158
- linha: numeroLinha,
159
- sugestao: messages.DetectorCssMensagens.fontStackMissingSugestao
160
- }));
161
- }
162
- }
163
- if (prop === 'z-index') {
164
- const zVal = parseInt(value, 10);
165
- if (!isNaN(zVal) && (zVal > 100 || zVal < -1)) {
166
- ocorrencias.push(criarOcorrencia({
167
- tipo: 'css-zindex-unmanaged',
168
- nivel: 'aviso',
169
- mensagem: messages.DetectorCssMensagens.zIndexUnmanaged,
170
- relPath,
171
- linha: numeroLinha,
172
- sugestao: messages.DetectorCssMensagens.zIndexUnmanagedSugestao
173
- }));
174
- }
175
- }
176
- if (/^(px|pt|pc|mm|cm|in)\b/.test(value) && (prop.startsWith('width') || prop.startsWith('height') || prop === 'font-size')) {
153
+ }
154
+ if (prop === 'z-index') {
155
+ const zVal = parseInt(value, 10);
156
+ if (!isNaN(zVal) && (zVal > 100 || zVal < -1)) {
177
157
  ocorrencias.push(criarOcorrencia({
178
- tipo: 'css-absolute-units',
179
- nivel: 'info',
180
- mensagem: messages.DetectorCssMensagens.absoluteUnits,
158
+ tipo: 'css-zindex-unmanaged',
159
+ nivel: 'aviso',
160
+ mensagem: messages.DetectorCssMensagens.zIndexUnmanaged,
181
161
  relPath,
182
162
  linha: numeroLinha,
183
- sugestao: messages.DetectorCssMensagens.absoluteUnitsSugestao
163
+ sugestao: messages.DetectorCssMensagens.zIndexUnmanagedSugestao
184
164
  }));
185
165
  }
186
- const missingPrefix = detectMissingVendorPrefix(prop);
187
- if (missingPrefix) {
166
+ }
167
+ if (/^(px|pt|pc|mm|cm|in)\b/.test(value) && (prop.startsWith('width') || prop.startsWith('height') || prop === 'font-size')) {
168
+ ocorrencias.push(criarOcorrencia({
169
+ tipo: 'css-absolute-units',
170
+ nivel: 'info',
171
+ mensagem: messages.DetectorCssMensagens.absoluteUnits,
172
+ relPath,
173
+ linha: numeroLinha,
174
+ sugestao: messages.DetectorCssMensagens.absoluteUnitsSugestao
175
+ }));
176
+ }
177
+ const missingPrefix = detectMissingVendorPrefix(prop);
178
+ if (missingPrefix) {
179
+ ocorrencias.push(criarOcorrencia({
180
+ tipo: 'css-vendor-prefix-missing',
181
+ nivel: 'aviso',
182
+ mensagem: messages.DetectorCssMensagens.vendorPrefixMissing,
183
+ relPath,
184
+ linha: numeroLinha,
185
+ sugestao: messages.DetectorCssMensagens.vendorPrefixMissingSugestao
186
+ }));
187
+ }
188
+ if (CSS_SHORTHAND_PROPS.has(prop)) {
189
+ const related = allDeclaredProps.filter(p => p.startsWith(`${prop}-`));
190
+ if (related.length >= 2) {
188
191
  ocorrencias.push(criarOcorrencia({
189
- tipo: 'css-vendor-prefix-missing',
190
- nivel: 'aviso',
191
- mensagem: messages.DetectorCssMensagens.vendorPrefixMissing,
192
+ tipo: 'css-missing-shorthand',
193
+ nivel: 'info',
194
+ mensagem: messages.DetectorCssMensagens.missingShorthand,
192
195
  relPath,
193
196
  linha: numeroLinha,
194
- sugestao: messages.DetectorCssMensagens.vendorPrefixMissingSugestao
197
+ sugestao: messages.DetectorCssMensagens.missingShorthandSugestao
195
198
  }));
196
199
  }
197
- if (CSS_SHORTHAND_PROPS.has(prop)) {
198
- const related = allDeclaredProps.filter(p => p.startsWith(`${prop}-`));
199
- if (related.length >= 2) {
200
- ocorrencias.push(criarOcorrencia({
201
- tipo: 'css-missing-shorthand',
202
- nivel: 'info',
203
- mensagem: messages.DetectorCssMensagens.missingShorthand,
204
- relPath,
205
- linha: numeroLinha,
206
- sugestao: messages.DetectorCssMensagens.missingShorthandSugestao
207
- }));
208
- }
209
- }
210
200
  }
201
+ }
202
+ linhas.forEach((linha, index) => {
203
+ const numeroLinha = index + 1;
204
+ const trimmed = linha.trim();
205
+ if (trimmed.includes('{')) {
206
+ processarAberturaBloco(linha, trimmed, numeroLinha);
207
+ }
208
+ if (trimmed.includes('}')) {
209
+ processarFechamentoBloco(numeroLinha);
210
+ }
211
+ if (/!important/.test(trimmed)) {
212
+ ocorrencias.push(criarOcorrencia({
213
+ tipo: 'css-important-usage',
214
+ nivel: 'aviso',
215
+ mensagem: messages.DetectorCssMensagens.important,
216
+ relPath,
217
+ linha: numeroLinha,
218
+ sugestao: messages.DetectorCssMensagens.importantSugestao
219
+ }));
220
+ }
221
+ processarPropriedade(trimmed, numeroLinha);
211
222
  if (/#[a-zA-Z0-9_-]+\s*\{/.test(trimmed)) {
212
223
  ocorrencias.push(criarOcorrencia({
213
224
  tipo: 'css-id-selector',
@@ -1,5 +1,27 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { splitLines } from '../../../shared/helpers/index.js';
3
+ function contarPropriedadesPorBloco(linhas) {
4
+ const props = new Map();
5
+ let inBlock = false;
6
+ for (const linha of linhas) {
7
+ if (linha.includes('{')) {
8
+ inBlock = true;
9
+ props.clear();
10
+ }
11
+ if (linha.includes('}')) {
12
+ inBlock = false;
13
+ continue;
14
+ }
15
+ if (inBlock) {
16
+ const match = /^\s*([a-zA-Z-]+)\s*:/.exec(linha);
17
+ if (match) {
18
+ const prop = match[1].toLowerCase();
19
+ props.set(prop, (props.get(prop) ?? 0) + 1);
20
+ }
21
+ }
22
+ }
23
+ return props;
24
+ }
3
25
  export function pontuarCss(src, _relPath) {
4
26
  let score = 0;
5
27
  const maxScore = 100;
@@ -20,24 +42,7 @@ export function pontuarCss(src, _relPath) {
20
42
  score += 3;
21
43
  issues.push(`${importantCount} usos de !important - refatore especificidade`);
22
44
  }
23
- const props = new Map();
24
- let inBlock = false;
25
- linhas.forEach(linha => {
26
- if (linha.includes('{')) {
27
- inBlock = true;
28
- props.clear();
29
- }
30
- if (linha.includes('}')) {
31
- inBlock = false;
32
- }
33
- if (inBlock) {
34
- const match = /^\s*([a-zA-Z-]+)\s*:/.exec(linha);
35
- if (match) {
36
- const prop = match[1].toLowerCase();
37
- props.set(prop, (props.get(prop) ?? 0) + 1);
38
- }
39
- }
40
- });
45
+ const props = contarPropriedadesPorBloco(linhas);
41
46
  const duplicates = [...props.values()].filter(valor => valor > 1).length;
42
47
  if (duplicates === 0) {
43
48
  score += 10;