@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.
- package/README.md +0 -34
- package/dist/analysts/architects/analyst-structure.js +19 -19
- package/dist/analysts/architects/archetypes-custom.js +8 -6
- package/dist/analysts/architects/detector-archetypes.js +30 -27
- package/dist/analysts/architects/signals-project-advanced.js +24 -26
- package/dist/analysts/architects/signals-project.js +1 -1
- package/dist/analysts/architects/strategist-structure.js +47 -66
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +21 -14
- package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
- package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
- package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -37
- package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
- package/dist/analysts/circleci/analysts/analyst-circleci.js +23 -15
- package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
- package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
- package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +20 -17
- package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
- package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
- package/dist/analysts/corrections/analyst-scoring.js +24 -21
- package/dist/analysts/corrections/corrector-structure.js +35 -35
- package/dist/analysts/corrections/fix-alias-imports.js +10 -6
- package/dist/analysts/corrections/fix-md-fences.js +23 -23
- package/dist/analysts/corrections/map-reversion.js +54 -51
- package/dist/analysts/corrections/pruning.js +19 -16
- package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
- package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
- package/dist/analysts/corrections/reorganize-barrels.js +10 -10
- package/dist/analysts/corrections/scoring-shared.js +19 -18
- package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
- package/dist/analysts/corrections/type-safety/type-validator.js +2 -1
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +36 -39
- package/dist/analysts/css/analysts/analyst-css.js +16 -15
- package/dist/analysts/css/corrections/correction-css.js +19 -11
- package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
- package/dist/analysts/css/scorers/scorer-css.js +23 -18
- package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
- package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +20 -15
- package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
- package/dist/analysts/css-in-js/shared.js +7 -0
- package/dist/analysts/detectors/detector-architecture.js +89 -83
- package/dist/analysts/detectors/detector-bugs-ml.js +21 -19
- package/dist/analysts/detectors/detector-code-fragile.js +30 -26
- package/dist/analysts/detectors/detector-duplications.js +21 -18
- package/dist/analysts/detectors/detector-monorepo.js +41 -36
- package/dist/analysts/detectors/detector-performance.js +22 -16
- package/dist/analysts/detectors/detector-recommendation-architecture.js +39 -35
- package/dist/analysts/detectors/detector-security.js +43 -42
- package/dist/analysts/detectors/detector-structure.js +29 -26
- package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +4 -1
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +10 -16
- package/dist/analysts/plugins/detector-documentation.js +1 -0
- package/dist/analysts/react/analysts/analyst-react-hooks.js +4 -2
- package/dist/analysts/react/analysts/analyst-react.js +1 -1
- package/dist/analysts/react/corrections/correction-react.js +2 -2
- package/dist/analysts/react/detectors/detector-react-best-practices.js +1 -1
- package/dist/analysts/registry/autodiscovery.js +2 -2
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +0 -8
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +1 -2
- package/dist/cli/commands/command-analysts.js +3 -2
- package/dist/cli/commands/command-formatters.js +1 -1
- package/dist/cli/commands/command-github-actions.js +1 -1
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-marketplace.js +4 -4
- package/dist/cli/diagnostic/filters.js +9 -9
- package/dist/core/config/chalk-safe.js +4 -4
- package/dist/core/config/config.js +5 -5
- package/dist/core/execution/analysis-cache.js +1 -1
- package/dist/core/execution/ast-cache.js +1 -3
- package/dist/core/execution/executor.js +2 -4
- package/dist/core/execution/scanner.js +1 -1
- package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
- package/dist/core/parsing/parser.js +2 -3
- package/dist/core/parsing/utils.js +1 -0
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/guardian/records.js +1 -0
- package/dist/guardian/sentinel.js +1 -0
- package/dist/guardian/watcher-hidden.js +1 -0
- package/dist/licenses/fs-utils.js +2 -1
- package/dist/licenses/header-options.js +1 -0
- package/dist/licenses/index.js +1 -1
- package/dist/reports/generator-report.js +3 -3
- package/dist/reports/report-patterns-usage.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +1 -1
- package/dist/shared/formatters/engines/stylelint.js +40 -34
- package/dist/shared/formatters/formatters/markdown.js +1 -1
- package/dist/shared/helpers/framework-detector.js +5 -1
- package/dist/shared/persistence/persistence.js +13 -5
- package/dist/shared/plugins/core-plugin.js +3 -1
- package/dist/types/analysts/tailwind-correction.js +2 -0
- package/dist/types/index.js +3 -3
- package/dist/types/processing/filters.js +5 -4
- package/dist/vulnerabilities/npm-audit.js +1 -0
- package/dist/vulnerabilities/scanner.js +1 -1
- package/dist/vulnerabilities/vulnerabilities.js +1 -0
- 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 (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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:
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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 =
|
|
283
|
+
const selectorText = rule.selector;
|
|
283
284
|
const simpleSelectors = splitSimpleSelectors(selectorText);
|
|
284
285
|
if (simpleSelectors.length) {
|
|
285
286
|
const decls = [];
|
|
286
|
-
rule.nodes
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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-
|
|
121
|
+
tipo: 'css-duplicate-property',
|
|
116
122
|
nivel: 'aviso',
|
|
117
|
-
mensagem: messages.DetectorCssMensagens.
|
|
123
|
+
mensagem: messages.DetectorCssMensagens.duplicateProperty(prop),
|
|
118
124
|
relPath,
|
|
119
125
|
linha: numeroLinha,
|
|
120
|
-
sugestao: messages.DetectorCssMensagens.
|
|
126
|
+
sugestao: messages.DetectorCssMensagens.duplicatePropertySugestao
|
|
121
127
|
}));
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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-
|
|
145
|
+
tipo: 'css-font-stack-missing',
|
|
142
146
|
nivel: 'info',
|
|
143
|
-
mensagem: messages.DetectorCssMensagens.
|
|
147
|
+
mensagem: messages.DetectorCssMensagens.fontStackMissing,
|
|
144
148
|
relPath,
|
|
145
149
|
linha: numeroLinha,
|
|
146
|
-
sugestao: messages.DetectorCssMensagens.
|
|
150
|
+
sugestao: messages.DetectorCssMensagens.fontStackMissingSugestao
|
|
147
151
|
}));
|
|
148
152
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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-
|
|
179
|
-
nivel: '
|
|
180
|
-
mensagem: messages.DetectorCssMensagens.
|
|
158
|
+
tipo: 'css-zindex-unmanaged',
|
|
159
|
+
nivel: 'aviso',
|
|
160
|
+
mensagem: messages.DetectorCssMensagens.zIndexUnmanaged,
|
|
181
161
|
relPath,
|
|
182
162
|
linha: numeroLinha,
|
|
183
|
-
sugestao: messages.DetectorCssMensagens.
|
|
163
|
+
sugestao: messages.DetectorCssMensagens.zIndexUnmanagedSugestao
|
|
184
164
|
}));
|
|
185
165
|
}
|
|
186
|
-
|
|
187
|
-
|
|
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-
|
|
190
|
-
nivel: '
|
|
191
|
-
mensagem: messages.DetectorCssMensagens.
|
|
192
|
+
tipo: 'css-missing-shorthand',
|
|
193
|
+
nivel: 'info',
|
|
194
|
+
mensagem: messages.DetectorCssMensagens.missingShorthand,
|
|
192
195
|
relPath,
|
|
193
196
|
linha: numeroLinha,
|
|
194
|
-
sugestao: messages.DetectorCssMensagens.
|
|
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 =
|
|
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;
|