@@ -42,15 +42,15 @@ const
42
42
ruleReplacements = require ( "../../conf/replacements.json" ) ;
43
43
const { getRuleFromConfig } = require ( "../config/flat-config-helpers" ) ;
44
44
const { FlatConfigArray } = require ( "../config/flat-config-array" ) ;
45
-
45
+ const { RuleValidator } = require ( "../config/rule-validator" ) ;
46
+ const { assertIsRuleOptions, assertIsRuleSeverity } = require ( "../config/flat-config-schema" ) ;
46
47
const debug = require ( "debug" ) ( "eslint:linter" ) ;
47
48
const MAX_AUTOFIX_PASSES = 10 ;
48
49
const DEFAULT_PARSER_NAME = "espree" ;
49
50
const DEFAULT_ECMA_VERSION = 5 ;
50
51
const commentParser = new ConfigCommentParser ( ) ;
51
52
const DEFAULT_ERROR_LOC = { start : { line : 1 , column : 0 } , end : { line : 1 , column : 1 } } ;
52
53
const parserSymbol = Symbol . for ( "eslint.RuleTester.parser" ) ;
53
- const globals = require ( "../../conf/globals" ) ;
54
54
55
55
//------------------------------------------------------------------------------
56
56
// Typedefs
@@ -145,29 +145,6 @@ function isEspree(parser) {
145
145
return ! ! ( parser === espree || parser [ parserSymbol ] === espree ) ;
146
146
}
147
147
148
- /**
149
- * Retrieves globals for the given ecmaVersion.
150
- * @param {number } ecmaVersion The version to retrieve globals for.
151
- * @returns {Object } The globals for the given ecmaVersion.
152
- */
153
- function getGlobalsForEcmaVersion ( ecmaVersion ) {
154
-
155
- switch ( ecmaVersion ) {
156
- case 3 :
157
- return globals . es3 ;
158
-
159
- case 5 :
160
- return globals . es5 ;
161
-
162
- default :
163
- if ( ecmaVersion < 2015 ) {
164
- return globals [ `es${ ecmaVersion + 2009 } ` ] ;
165
- }
166
-
167
- return globals [ `es${ ecmaVersion } ` ] ;
168
- }
169
- }
170
-
171
148
/**
172
149
* Ensures that variables representing built-in properties of the Global Object,
173
150
* and any globals declared by special block comments, are present in the global
@@ -361,13 +338,13 @@ function extractDirectiveComment(value) {
361
338
* Parses comments in file to extract file-specific config of rules, globals
362
339
* and environments and merges them with global config; also code blocks
363
340
* where reporting is disabled or enabled and merges them with reporting config.
364
- * @param {ASTNode } ast The top node of the AST .
341
+ * @param {SourceCode } sourceCode The SourceCode object to get comments from .
365
342
* @param {function(string): {create: Function} } ruleMapper A map from rule IDs to defined rules
366
343
* @param {string|null } warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
367
344
* @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]} }
368
345
* A collection of the directive comments that were found, along with any problems that occurred when parsing
369
346
*/
370
- function getDirectiveComments ( ast , ruleMapper , warnInlineConfig ) {
347
+ function getDirectiveComments ( sourceCode , ruleMapper , warnInlineConfig ) {
371
348
const configuredRules = { } ;
372
349
const enabledGlobals = Object . create ( null ) ;
373
350
const exportedVariables = { } ;
@@ -377,7 +354,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
377
354
builtInRules : Rules
378
355
} ) ;
379
356
380
- ast . comments . filter ( token => token . type !== "Shebang" ) . forEach ( comment => {
357
+ sourceCode . getInlineConfigNodes ( ) . filter ( token => token . type !== "Shebang" ) . forEach ( comment => {
381
358
const { directivePart, justificationPart } = extractDirectiveComment ( comment . value ) ;
382
359
383
360
const match = directivesPattern . exec ( directivePart ) ;
@@ -511,6 +488,69 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
511
488
} ;
512
489
}
513
490
491
+ /**
492
+ * Parses comments in file to extract disable directives.
493
+ * @param {SourceCode } sourceCode The SourceCode object to get comments from.
494
+ * @param {function(string): {create: Function} } ruleMapper A map from rule IDs to defined rules
495
+ * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]} }
496
+ * A collection of the directive comments that were found, along with any problems that occurred when parsing
497
+ */
498
+ function getDirectiveCommentsForFlatConfig ( sourceCode , ruleMapper ) {
499
+ const problems = [ ] ;
500
+ const disableDirectives = [ ] ;
501
+
502
+ sourceCode . getInlineConfigNodes ( ) . filter ( token => token . type !== "Shebang" ) . forEach ( comment => {
503
+ const { directivePart, justificationPart } = extractDirectiveComment ( comment . value ) ;
504
+
505
+ const match = directivesPattern . exec ( directivePart ) ;
506
+
507
+ if ( ! match ) {
508
+ return ;
509
+ }
510
+ const directiveText = match [ 1 ] ;
511
+ const lineCommentSupported = / ^ e s l i n t - d i s a b l e - ( n e x t - ) ? l i n e $ / u. test ( directiveText ) ;
512
+
513
+ if ( comment . type === "Line" && ! lineCommentSupported ) {
514
+ return ;
515
+ }
516
+
517
+ if ( directiveText === "eslint-disable-line" && comment . loc . start . line !== comment . loc . end . line ) {
518
+ const message = `${ directiveText } comment should not span multiple lines.` ;
519
+
520
+ problems . push ( createLintingProblem ( {
521
+ ruleId : null ,
522
+ message,
523
+ loc : comment . loc
524
+ } ) ) ;
525
+ return ;
526
+ }
527
+
528
+ const directiveValue = directivePart . slice ( match . index + directiveText . length ) ;
529
+
530
+ switch ( directiveText ) {
531
+ case "eslint-disable" :
532
+ case "eslint-enable" :
533
+ case "eslint-disable-next-line" :
534
+ case "eslint-disable-line" : {
535
+ const directiveType = directiveText . slice ( "eslint-" . length ) ;
536
+ const options = { commentToken : comment , type : directiveType , value : directiveValue , justification : justificationPart , ruleMapper } ;
537
+ const { directives, directiveProblems } = createDisableDirectives ( options ) ;
538
+
539
+ disableDirectives . push ( ...directives ) ;
540
+ problems . push ( ...directiveProblems ) ;
541
+ break ;
542
+ }
543
+
544
+ // no default
545
+ }
546
+ } ) ;
547
+
548
+ return {
549
+ problems,
550
+ disableDirectives
551
+ } ;
552
+ }
553
+
514
554
/**
515
555
* Normalize ECMAScript version from the initial config
516
556
* @param {Parser } parser The parser which uses this options.
@@ -1313,7 +1353,7 @@ class Linter {
1313
1353
1314
1354
const sourceCode = slots . lastSourceCode ;
1315
1355
const commentDirectives = options . allowInlineConfig
1316
- ? getDirectiveComments ( sourceCode . ast , ruleId => getRule ( slots , ruleId ) , options . warnInlineConfig )
1356
+ ? getDirectiveComments ( sourceCode , ruleId => getRule ( slots , ruleId ) , options . warnInlineConfig )
1317
1357
: { configuredRules : { } , enabledGlobals : { } , exportedVariables : { } , problems : [ ] , disableDirectives : [ ] } ;
1318
1358
1319
1359
// augment global scope with declared global variables
@@ -1324,7 +1364,6 @@ class Linter {
1324
1364
) ;
1325
1365
1326
1366
const configuredRules = Object . assign ( { } , config . rules , commentDirectives . configuredRules ) ;
1327
-
1328
1367
let lintingProblems ;
1329
1368
1330
1369
try {
@@ -1540,19 +1579,6 @@ class Linter {
1540
1579
languageOptions . ecmaVersion
1541
1580
) ;
1542
1581
1543
- /*
1544
- * add configured globals and language globals
1545
- *
1546
- * using Object.assign instead of object spread for performance reasons
1547
- * https://github.com/eslint/eslint/issues/16302
1548
- */
1549
- const configuredGlobals = Object . assign (
1550
- { } ,
1551
- getGlobalsForEcmaVersion ( languageOptions . ecmaVersion ) ,
1552
- languageOptions . sourceType === "commonjs" ? globals . commonjs : void 0 ,
1553
- languageOptions . globals
1554
- ) ;
1555
-
1556
1582
// double check that there is a parser to avoid mysterious error messages
1557
1583
if ( ! languageOptions . parser ) {
1558
1584
throw new TypeError ( `No parser specified for ${ options . filename } ` ) ;
@@ -1608,25 +1634,113 @@ class Linter {
1608
1634
}
1609
1635
1610
1636
const sourceCode = slots . lastSourceCode ;
1611
- const commentDirectives = options . allowInlineConfig
1612
- ? getDirectiveComments (
1613
- sourceCode . ast ,
1614
- ruleId => getRuleFromConfig ( ruleId , config ) ,
1615
- options . warnInlineConfig
1616
- )
1617
- : { configuredRules : { } , enabledGlobals : { } , exportedVariables : { } , problems : [ ] , disableDirectives : [ ] } ;
1618
1637
1619
- // augment global scope with declared global variables
1620
- addDeclaredGlobals (
1621
- sourceCode . scopeManager . scopes [ 0 ] ,
1622
- configuredGlobals ,
1623
- { exportedVariables : commentDirectives . exportedVariables , enabledGlobals : commentDirectives . enabledGlobals }
1624
- ) ;
1638
+ /*
1639
+ * Make adjustments based on the language options. For JavaScript,
1640
+ * this is primarily about adding variables into the global scope
1641
+ * to account for ecmaVersion and configured globals.
1642
+ */
1643
+ sourceCode . applyLanguageOptions ( languageOptions ) ;
1625
1644
1626
- const configuredRules = Object . assign ( { } , config . rules , commentDirectives . configuredRules ) ;
1645
+ const mergedInlineConfig = {
1646
+ rules : { }
1647
+ } ;
1648
+ const inlineConfigProblems = [ ] ;
1649
+
1650
+ /*
1651
+ * Inline config can be either enabled or disabled. If disabled, it's possible
1652
+ * to detect the inline config and emit a warning (though this is not required).
1653
+ * So we first check to see if inline config is allowed at all, and if so, we
1654
+ * need to check if it's a warning or not.
1655
+ */
1656
+ if ( options . allowInlineConfig ) {
1657
+
1658
+ // if inline config should warn then add the warnings
1659
+ if ( options . warnInlineConfig ) {
1660
+ sourceCode . getInlineConfigNodes ( ) . forEach ( node => {
1661
+ inlineConfigProblems . push ( createLintingProblem ( {
1662
+ ruleId : null ,
1663
+ message : `'${ sourceCode . text . slice ( node . range [ 0 ] , node . range [ 1 ] ) } ' has no effect because you have 'noInlineConfig' setting in ${ options . warnInlineConfig } .` ,
1664
+ loc : node . loc ,
1665
+ severity : 1
1666
+ } ) ) ;
1667
+
1668
+ } ) ;
1669
+ } else {
1670
+ const inlineConfigResult = sourceCode . applyInlineConfig ( ) ;
1671
+
1672
+ inlineConfigProblems . push (
1673
+ ...inlineConfigResult . problems
1674
+ . map ( createLintingProblem )
1675
+ . map ( problem => {
1676
+ problem . fatal = true ;
1677
+ return problem ;
1678
+ } )
1679
+ ) ;
1680
+
1681
+ // next we need to verify information about the specified rules
1682
+ const ruleValidator = new RuleValidator ( ) ;
1683
+
1684
+ for ( const { config : inlineConfig , node } of inlineConfigResult . configs ) {
1685
+
1686
+ Object . keys ( inlineConfig . rules ) . forEach ( ruleId => {
1687
+ const rule = getRuleFromConfig ( ruleId , config ) ;
1688
+ const ruleValue = inlineConfig . rules [ ruleId ] ;
1689
+
1690
+ if ( ! rule ) {
1691
+ inlineConfigProblems . push ( createLintingProblem ( { ruleId, loc : node . loc } ) ) ;
1692
+ return ;
1693
+ }
1694
+
1695
+ try {
1696
+
1697
+ const ruleOptions = Array . isArray ( ruleValue ) ? ruleValue : [ ruleValue ] ;
1698
+
1699
+ assertIsRuleOptions ( ruleId , ruleValue ) ;
1700
+ assertIsRuleSeverity ( ruleId , ruleOptions [ 0 ] ) ;
1701
+
1702
+ ruleValidator . validate ( {
1703
+ plugins : config . plugins ,
1704
+ rules : {
1705
+ [ ruleId ] : ruleOptions
1706
+ }
1707
+ } ) ;
1708
+ mergedInlineConfig . rules [ ruleId ] = ruleValue ;
1709
+ } catch ( err ) {
1710
+
1711
+ let baseMessage = err . message . slice (
1712
+ err . message . startsWith ( "Key \"rules\":" )
1713
+ ? err . message . indexOf ( ":" , 12 ) + 1
1714
+ : err . message . indexOf ( ":" ) + 1
1715
+ ) . trim ( ) ;
1716
+
1717
+ if ( err . messageTemplate ) {
1718
+ baseMessage += ` You passed "${ ruleValue } ".` ;
1719
+ }
1720
+
1721
+ inlineConfigProblems . push ( createLintingProblem ( {
1722
+ ruleId,
1723
+ message : `Inline configuration for rule "${ ruleId } " is invalid:\n\t${ baseMessage } \n` ,
1724
+ loc : node . loc
1725
+ } ) ) ;
1726
+ }
1727
+ } ) ;
1728
+ }
1729
+ }
1730
+ }
1627
1731
1732
+ const commentDirectives = options . allowInlineConfig && ! options . warnInlineConfig
1733
+ ? getDirectiveCommentsForFlatConfig (
1734
+ sourceCode ,
1735
+ ruleId => getRuleFromConfig ( ruleId , config )
1736
+ )
1737
+ : { problems : [ ] , disableDirectives : [ ] } ;
1738
+
1739
+ const configuredRules = Object . assign ( { } , config . rules , mergedInlineConfig . rules ) ;
1628
1740
let lintingProblems ;
1629
1741
1742
+ sourceCode . finalize ( ) ;
1743
+
1630
1744
try {
1631
1745
lintingProblems = runRules (
1632
1746
sourceCode ,
@@ -1667,6 +1781,7 @@ class Linter {
1667
1781
disableFixes : options . disableFixes ,
1668
1782
problems : lintingProblems
1669
1783
. concat ( commentDirectives . problems )
1784
+ . concat ( inlineConfigProblems )
1670
1785
. sort ( ( problemA , problemB ) => problemA . line - problemB . line || problemA . column - problemB . column ) ,
1671
1786
reportUnusedDisableDirectives : options . reportUnusedDisableDirectives
1672
1787
} ) ;
0 commit comments