pumuki 6.3.270 → 6.3.271
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/CHANGELOG.md +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/android.test.ts +538 -0
- package/core/facts/detectors/text/android.ts +436 -0
- package/core/facts/detectors/text/ios.test.ts +328 -1
- package/core/facts/detectors/text/ios.ts +241 -0
- package/core/facts/detectors/typescript/index.test.ts +393 -0
- package/core/facts/detectors/typescript/index.ts +316 -0
- package/core/facts/extractHeuristicFacts.ts +70 -1
- package/core/rules/presets/heuristics/android.test.ts +91 -1
- package/core/rules/presets/heuristics/android.ts +360 -0
- package/core/rules/presets/heuristics/ios.test.ts +54 -1
- package/core/rules/presets/heuristics/ios.ts +243 -2
- package/core/rules/presets/heuristics/typescript.test.ts +50 -2
- package/core/rules/presets/heuristics/typescript.ts +162 -0
- package/docs/operations/RELEASE_NOTES.md +4 -0
- package/integrations/config/skillsDetectorRegistry.ts +501 -0
- package/integrations/config/skillsRuleClassification.ts +127 -3
- package/integrations/git/runPlatformGate.ts +4 -1
- package/integrations/lifecycle/preWriteAutomation.ts +1 -0
- package/integrations/lifecycle/preWriteLease.ts +41 -4
- package/package.json +1 -1
- package/scripts/classify-skills-rules.ts +2 -2
- package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
- package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
- package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
- package/scripts/framework-menu-gate-lib.ts +86 -1
- package/scripts/framework-menu-layout-data.ts +3 -3
- package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
- package/scripts/framework-menu.ts +10 -6
- package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
- package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
|
@@ -19,16 +19,25 @@ import {
|
|
|
19
19
|
hasConsoleLogCall,
|
|
20
20
|
hasDebuggerStatement,
|
|
21
21
|
hasDeleteOperator,
|
|
22
|
+
hasDefaultExportedApiRouteHandler,
|
|
23
|
+
hasDirectNetworkCall,
|
|
22
24
|
hasEmptyCatchClause,
|
|
23
25
|
hasEvalCall,
|
|
24
26
|
hasExplicitAnyType,
|
|
25
27
|
hasFrameworkDependencyImport,
|
|
26
28
|
hasFunctionConstructorUsage,
|
|
29
|
+
hasInterpolatedUnsafeSqlCall,
|
|
30
|
+
hasNestedCallbackUsage,
|
|
31
|
+
hasNestedIfElseStatement,
|
|
32
|
+
hasNonSemanticClickableJsx,
|
|
33
|
+
hasSensitiveTokenInUrl,
|
|
34
|
+
hasWeakBcryptSaltRounds,
|
|
27
35
|
hasNetworkCallWithoutErrorHandling,
|
|
28
36
|
hasMixedCommandQueryClass,
|
|
29
37
|
hasMixedCommandQueryInterface,
|
|
30
38
|
hasUnknownWithoutGuard,
|
|
31
39
|
hasRecordStringUnknownType,
|
|
40
|
+
hasRedundantAriaRoleOnSemanticJsx,
|
|
32
41
|
hasOverrideMethodThrowingNotImplemented,
|
|
33
42
|
hasLargeClassDeclaration,
|
|
34
43
|
hasSetIntervalStringCallback,
|
|
@@ -239,6 +248,390 @@ test('hasAsyncPromiseExecutor detecta ejecutor async en new Promise', () => {
|
|
|
239
248
|
assert.equal(hasAsyncPromiseExecutor(syncExecutorAst), false);
|
|
240
249
|
});
|
|
241
250
|
|
|
251
|
+
test('hasWeakBcryptSaltRounds detecta bcrypt con salt rounds inferiores a 10', () => {
|
|
252
|
+
const weakHashAst = {
|
|
253
|
+
type: 'CallExpression',
|
|
254
|
+
callee: {
|
|
255
|
+
type: 'MemberExpression',
|
|
256
|
+
object: { type: 'Identifier', name: 'bcrypt' },
|
|
257
|
+
property: { type: 'Identifier', name: 'hash' },
|
|
258
|
+
},
|
|
259
|
+
arguments: [
|
|
260
|
+
{ type: 'Identifier', name: 'password' },
|
|
261
|
+
{ type: 'NumericLiteral', value: 8 },
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
const strongHashAst = {
|
|
265
|
+
type: 'CallExpression',
|
|
266
|
+
callee: {
|
|
267
|
+
type: 'MemberExpression',
|
|
268
|
+
object: { type: 'Identifier', name: 'bcrypt' },
|
|
269
|
+
property: { type: 'Identifier', name: 'hashSync' },
|
|
270
|
+
},
|
|
271
|
+
arguments: [
|
|
272
|
+
{ type: 'Identifier', name: 'password' },
|
|
273
|
+
{ type: 'NumericLiteral', value: 10 },
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
const configuredRoundsAst = {
|
|
277
|
+
type: 'CallExpression',
|
|
278
|
+
callee: {
|
|
279
|
+
type: 'MemberExpression',
|
|
280
|
+
object: { type: 'Identifier', name: 'bcrypt' },
|
|
281
|
+
property: { type: 'Identifier', name: 'hash' },
|
|
282
|
+
},
|
|
283
|
+
arguments: [
|
|
284
|
+
{ type: 'Identifier', name: 'password' },
|
|
285
|
+
{ type: 'Identifier', name: 'saltRounds' },
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
assert.equal(hasWeakBcryptSaltRounds(weakHashAst), true);
|
|
290
|
+
assert.equal(hasWeakBcryptSaltRounds(strongHashAst), false);
|
|
291
|
+
assert.equal(hasWeakBcryptSaltRounds(configuredRoundsAst), false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('hasInterpolatedUnsafeSqlCall detecta SQL interpolado en llamadas no parametrizadas', () => {
|
|
295
|
+
const unsafeQueryAst = {
|
|
296
|
+
type: 'CallExpression',
|
|
297
|
+
callee: {
|
|
298
|
+
type: 'MemberExpression',
|
|
299
|
+
object: { type: 'Identifier', name: 'db' },
|
|
300
|
+
property: { type: 'Identifier', name: 'query' },
|
|
301
|
+
},
|
|
302
|
+
arguments: [
|
|
303
|
+
{
|
|
304
|
+
type: 'TemplateLiteral',
|
|
305
|
+
expressions: [{ type: 'Identifier', name: 'userId' }],
|
|
306
|
+
quasis: [],
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
const unsafePrismaAst = {
|
|
311
|
+
type: 'CallExpression',
|
|
312
|
+
callee: {
|
|
313
|
+
type: 'MemberExpression',
|
|
314
|
+
object: { type: 'Identifier', name: 'prisma' },
|
|
315
|
+
property: { type: 'Identifier', name: '$queryRawUnsafe' },
|
|
316
|
+
},
|
|
317
|
+
arguments: [
|
|
318
|
+
{
|
|
319
|
+
type: 'TemplateLiteral',
|
|
320
|
+
expressions: [{ type: 'Identifier', name: 'email' }],
|
|
321
|
+
quasis: [],
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
const staticQueryAst = {
|
|
326
|
+
type: 'CallExpression',
|
|
327
|
+
callee: {
|
|
328
|
+
type: 'MemberExpression',
|
|
329
|
+
object: { type: 'Identifier', name: 'db' },
|
|
330
|
+
property: { type: 'Identifier', name: 'query' },
|
|
331
|
+
},
|
|
332
|
+
arguments: [
|
|
333
|
+
{
|
|
334
|
+
type: 'TemplateLiteral',
|
|
335
|
+
expressions: [],
|
|
336
|
+
quasis: [],
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
};
|
|
340
|
+
const taggedTemplateAst = {
|
|
341
|
+
type: 'TaggedTemplateExpression',
|
|
342
|
+
tag: {
|
|
343
|
+
type: 'MemberExpression',
|
|
344
|
+
object: { type: 'Identifier', name: 'prisma' },
|
|
345
|
+
property: { type: 'Identifier', name: '$queryRaw' },
|
|
346
|
+
},
|
|
347
|
+
quasi: {
|
|
348
|
+
type: 'TemplateLiteral',
|
|
349
|
+
expressions: [{ type: 'Identifier', name: 'userId' }],
|
|
350
|
+
quasis: [],
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
assert.equal(hasInterpolatedUnsafeSqlCall(unsafeQueryAst), true);
|
|
355
|
+
assert.equal(hasInterpolatedUnsafeSqlCall(unsafePrismaAst), true);
|
|
356
|
+
assert.equal(hasInterpolatedUnsafeSqlCall(staticQueryAst), false);
|
|
357
|
+
assert.equal(hasInterpolatedUnsafeSqlCall(taggedTemplateAst), false);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('hasSensitiveTokenInUrl detecta tokens sensibles en URL de llamadas de red', () => {
|
|
361
|
+
const fetchTokenAst = {
|
|
362
|
+
type: 'CallExpression',
|
|
363
|
+
callee: { type: 'Identifier', name: 'fetch' },
|
|
364
|
+
arguments: [{ type: 'StringLiteral', value: '/api/me?access_token=abc' }],
|
|
365
|
+
};
|
|
366
|
+
const axiosTemplateTokenAst = {
|
|
367
|
+
type: 'CallExpression',
|
|
368
|
+
callee: {
|
|
369
|
+
type: 'MemberExpression',
|
|
370
|
+
object: { type: 'Identifier', name: 'axios' },
|
|
371
|
+
property: { type: 'Identifier', name: 'get' },
|
|
372
|
+
},
|
|
373
|
+
arguments: [
|
|
374
|
+
{
|
|
375
|
+
type: 'TemplateLiteral',
|
|
376
|
+
expressions: [{ type: 'Identifier', name: 'token' }],
|
|
377
|
+
quasis: [{ type: 'TemplateElement', value: { cooked: '/api/me?token=' } }],
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
const headerTokenAst = {
|
|
382
|
+
type: 'CallExpression',
|
|
383
|
+
callee: { type: 'Identifier', name: 'fetch' },
|
|
384
|
+
arguments: [
|
|
385
|
+
{ type: 'StringLiteral', value: '/api/me' },
|
|
386
|
+
{
|
|
387
|
+
type: 'ObjectExpression',
|
|
388
|
+
properties: [
|
|
389
|
+
{
|
|
390
|
+
type: 'ObjectProperty',
|
|
391
|
+
key: { type: 'Identifier', name: 'headers' },
|
|
392
|
+
value: { type: 'ObjectExpression', properties: [] },
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
assert.equal(hasSensitiveTokenInUrl(fetchTokenAst), true);
|
|
400
|
+
assert.equal(hasSensitiveTokenInUrl(axiosTemplateTokenAst), true);
|
|
401
|
+
assert.equal(hasSensitiveTokenInUrl(headerTokenAst), false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('hasDirectNetworkCall detecta fetch y axios directos', () => {
|
|
405
|
+
const fetchAst = {
|
|
406
|
+
type: 'CallExpression',
|
|
407
|
+
callee: { type: 'Identifier', name: 'fetch' },
|
|
408
|
+
arguments: [{ type: 'StringLiteral', value: '/api/stores' }],
|
|
409
|
+
};
|
|
410
|
+
const axiosAst = {
|
|
411
|
+
type: 'CallExpression',
|
|
412
|
+
callee: {
|
|
413
|
+
type: 'MemberExpression',
|
|
414
|
+
object: { type: 'Identifier', name: 'axios' },
|
|
415
|
+
property: { type: 'Identifier', name: 'get' },
|
|
416
|
+
},
|
|
417
|
+
arguments: [{ type: 'StringLiteral', value: '/api/stores' }],
|
|
418
|
+
};
|
|
419
|
+
const mswAst = {
|
|
420
|
+
type: 'CallExpression',
|
|
421
|
+
callee: {
|
|
422
|
+
type: 'MemberExpression',
|
|
423
|
+
object: { type: 'Identifier', name: 'server' },
|
|
424
|
+
property: { type: 'Identifier', name: 'use' },
|
|
425
|
+
},
|
|
426
|
+
arguments: [],
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
assert.equal(hasDirectNetworkCall(fetchAst), true);
|
|
430
|
+
assert.equal(hasDirectNetworkCall(axiosAst), true);
|
|
431
|
+
assert.equal(hasDirectNetworkCall(mswAst), false);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('hasNonSemanticClickableJsx detecta elementos no semanticos clicables sin teclado', () => {
|
|
435
|
+
const clickableDivAst = {
|
|
436
|
+
type: 'JSXOpeningElement',
|
|
437
|
+
name: { type: 'JSXIdentifier', name: 'div' },
|
|
438
|
+
attributes: [
|
|
439
|
+
{ type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
|
|
440
|
+
],
|
|
441
|
+
};
|
|
442
|
+
const keyboardAccessibleDivAst = {
|
|
443
|
+
type: 'JSXOpeningElement',
|
|
444
|
+
name: { type: 'JSXIdentifier', name: 'div' },
|
|
445
|
+
attributes: [
|
|
446
|
+
{ type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
|
|
447
|
+
{ type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onKeyDown' } },
|
|
448
|
+
{ type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'tabIndex' } },
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
const buttonAst = {
|
|
452
|
+
type: 'JSXOpeningElement',
|
|
453
|
+
name: { type: 'JSXIdentifier', name: 'button' },
|
|
454
|
+
attributes: [
|
|
455
|
+
{ type: 'JSXAttribute', name: { type: 'JSXIdentifier', name: 'onClick' } },
|
|
456
|
+
],
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
assert.equal(hasNonSemanticClickableJsx(clickableDivAst), true);
|
|
460
|
+
assert.equal(hasNonSemanticClickableJsx(keyboardAccessibleDivAst), false);
|
|
461
|
+
assert.equal(hasNonSemanticClickableJsx(buttonAst), false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('hasRedundantAriaRoleOnSemanticJsx detecta roles ARIA redundantes en HTML semantico', () => {
|
|
465
|
+
const redundantButtonRoleAst = {
|
|
466
|
+
type: 'JSXOpeningElement',
|
|
467
|
+
name: { type: 'JSXIdentifier', name: 'button' },
|
|
468
|
+
attributes: [
|
|
469
|
+
{
|
|
470
|
+
type: 'JSXAttribute',
|
|
471
|
+
name: { type: 'JSXIdentifier', name: 'role' },
|
|
472
|
+
value: { type: 'StringLiteral', value: 'button' },
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
const expressionRoleAst = {
|
|
477
|
+
type: 'JSXOpeningElement',
|
|
478
|
+
name: { type: 'JSXIdentifier', name: 'nav' },
|
|
479
|
+
attributes: [
|
|
480
|
+
{
|
|
481
|
+
type: 'JSXAttribute',
|
|
482
|
+
name: { type: 'JSXIdentifier', name: 'role' },
|
|
483
|
+
value: {
|
|
484
|
+
type: 'JSXExpressionContainer',
|
|
485
|
+
expression: { type: 'StringLiteral', value: 'navigation' },
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
};
|
|
490
|
+
const necessaryRoleAst = {
|
|
491
|
+
type: 'JSXOpeningElement',
|
|
492
|
+
name: { type: 'JSXIdentifier', name: 'div' },
|
|
493
|
+
attributes: [
|
|
494
|
+
{
|
|
495
|
+
type: 'JSXAttribute',
|
|
496
|
+
name: { type: 'JSXIdentifier', name: 'role' },
|
|
497
|
+
value: { type: 'StringLiteral', value: 'dialog' },
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
assert.equal(hasRedundantAriaRoleOnSemanticJsx(redundantButtonRoleAst), true);
|
|
503
|
+
assert.equal(hasRedundantAriaRoleOnSemanticJsx(expressionRoleAst), true);
|
|
504
|
+
assert.equal(hasRedundantAriaRoleOnSemanticJsx(necessaryRoleAst), false);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('hasNestedCallbackUsage detecta callback anidado en llamadas', () => {
|
|
508
|
+
const ast = {
|
|
509
|
+
type: 'Program',
|
|
510
|
+
body: [
|
|
511
|
+
{
|
|
512
|
+
type: 'CallExpression',
|
|
513
|
+
callee: { type: 'Identifier', name: 'loadUser' },
|
|
514
|
+
arguments: [
|
|
515
|
+
{
|
|
516
|
+
type: 'ArrowFunctionExpression',
|
|
517
|
+
body: {
|
|
518
|
+
type: 'BlockStatement',
|
|
519
|
+
body: [
|
|
520
|
+
{
|
|
521
|
+
type: 'CallExpression',
|
|
522
|
+
callee: { type: 'Identifier', name: 'loadOrders' },
|
|
523
|
+
arguments: [
|
|
524
|
+
{
|
|
525
|
+
type: 'FunctionExpression',
|
|
526
|
+
body: { type: 'BlockStatement', body: [] },
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
assert.equal(hasNestedCallbackUsage(ast), true);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test('hasNestedCallbackUsage permite callbacks planos y funciones internas no pasadas como callback', () => {
|
|
542
|
+
const flatCallbackAst = {
|
|
543
|
+
type: 'CallExpression',
|
|
544
|
+
callee: { type: 'Identifier', name: 'loadUser' },
|
|
545
|
+
arguments: [
|
|
546
|
+
{
|
|
547
|
+
type: 'ArrowFunctionExpression',
|
|
548
|
+
body: { type: 'BlockStatement', body: [] },
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
};
|
|
552
|
+
const innerFunctionAst = {
|
|
553
|
+
type: 'ArrowFunctionExpression',
|
|
554
|
+
body: {
|
|
555
|
+
type: 'BlockStatement',
|
|
556
|
+
body: [
|
|
557
|
+
{
|
|
558
|
+
type: 'FunctionDeclaration',
|
|
559
|
+
id: { type: 'Identifier', name: 'helper' },
|
|
560
|
+
body: { type: 'BlockStatement', body: [] },
|
|
561
|
+
},
|
|
562
|
+
],
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
assert.equal(hasNestedCallbackUsage(flatCallbackAst), false);
|
|
567
|
+
assert.equal(hasNestedCallbackUsage(innerFunctionAst), false);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test('hasNestedIfElseStatement detecta if else anidado y preserva early return', () => {
|
|
571
|
+
const nestedIfElseAst = {
|
|
572
|
+
type: 'IfStatement',
|
|
573
|
+
test: { type: 'Identifier', name: 'isReady' },
|
|
574
|
+
consequent: {
|
|
575
|
+
type: 'BlockStatement',
|
|
576
|
+
body: [
|
|
577
|
+
{
|
|
578
|
+
type: 'IfStatement',
|
|
579
|
+
test: { type: 'Identifier', name: 'hasPermission' },
|
|
580
|
+
consequent: { type: 'BlockStatement', body: [] },
|
|
581
|
+
alternate: null,
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
},
|
|
585
|
+
alternate: { type: 'BlockStatement', body: [] },
|
|
586
|
+
};
|
|
587
|
+
const elseIfAst = {
|
|
588
|
+
type: 'IfStatement',
|
|
589
|
+
test: { type: 'Identifier', name: 'isReady' },
|
|
590
|
+
consequent: { type: 'BlockStatement', body: [] },
|
|
591
|
+
alternate: {
|
|
592
|
+
type: 'IfStatement',
|
|
593
|
+
test: { type: 'Identifier', name: 'isFallback' },
|
|
594
|
+
consequent: { type: 'BlockStatement', body: [] },
|
|
595
|
+
alternate: null,
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
const earlyReturnAst = {
|
|
599
|
+
type: 'IfStatement',
|
|
600
|
+
test: { type: 'Identifier', name: 'missing' },
|
|
601
|
+
consequent: {
|
|
602
|
+
type: 'BlockStatement',
|
|
603
|
+
body: [{ type: 'ReturnStatement' }],
|
|
604
|
+
},
|
|
605
|
+
alternate: null,
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
assert.equal(hasNestedIfElseStatement(nestedIfElseAst), true);
|
|
609
|
+
assert.equal(hasNestedIfElseStatement(elseIfAst), true);
|
|
610
|
+
assert.equal(hasNestedIfElseStatement(earlyReturnAst), false);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test('hasDefaultExportedApiRouteHandler detecta handlers default de pages api', () => {
|
|
614
|
+
const defaultFunctionAst = {
|
|
615
|
+
type: 'ExportDefaultDeclaration',
|
|
616
|
+
declaration: { type: 'FunctionDeclaration', id: { type: 'Identifier', name: 'handler' } },
|
|
617
|
+
};
|
|
618
|
+
const defaultIdentifierAst = {
|
|
619
|
+
type: 'ExportDefaultDeclaration',
|
|
620
|
+
declaration: { type: 'Identifier', name: 'handler' },
|
|
621
|
+
};
|
|
622
|
+
const namedRouteHandlerAst = {
|
|
623
|
+
type: 'ExportNamedDeclaration',
|
|
624
|
+
declaration: {
|
|
625
|
+
type: 'FunctionDeclaration',
|
|
626
|
+
id: { type: 'Identifier', name: 'GET' },
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
assert.equal(hasDefaultExportedApiRouteHandler(defaultFunctionAst), true);
|
|
631
|
+
assert.equal(hasDefaultExportedApiRouteHandler(defaultIdentifierAst), true);
|
|
632
|
+
assert.equal(hasDefaultExportedApiRouteHandler(namedRouteHandlerAst), false);
|
|
633
|
+
});
|
|
634
|
+
|
|
242
635
|
test('hasWithStatement detecta with y hasDeleteOperator detecta operador delete', () => {
|
|
243
636
|
const withAst = {
|
|
244
637
|
type: 'Program',
|