-
Notifications
You must be signed in to change notification settings - Fork 8
linter: add deprecation code check, linter declarations #212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
'use strict'; | ||
|
||
// Validates a deprecation header from doc/api/deprecation.md and captures the | ||
// code | ||
// For example, `DEP0001: `http.OutgoingMessage.prototype.flush` captures `0001` | ||
export const DEPRECATION_HEADER_REGEX = /DEP(\d{4}): .+?/; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
'use strict'; | ||
|
||
import { LINT_MESSAGES } from '../constants.mjs'; | ||
import createLinterEngine from './engine.mjs'; | ||
import reporters from './reporters/index.mjs'; | ||
import rules from './rules/index.mjs'; | ||
|
@@ -22,6 +23,13 @@ const createLinter = (dryRun, disabledRules) => { | |
.map(([, rule]) => rule); | ||
}; | ||
|
||
/** | ||
* @type {import('./types').LintDeclarations} | ||
*/ | ||
const declarations = { | ||
skipDeprecation: [], | ||
}; | ||
|
||
const engine = createLinterEngine(getEnabledRules(disabledRules)); | ||
|
||
/** | ||
|
@@ -37,7 +45,7 @@ const createLinter = (dryRun, disabledRules) => { | |
* @param entries | ||
*/ | ||
const lintAll = entries => { | ||
issues.push(...engine.lintAll(entries)); | ||
issues.push(...engine.lintAll(entries, declarations)); | ||
}; | ||
|
||
/** | ||
|
@@ -58,6 +66,87 @@ const createLinter = (dryRun, disabledRules) => { | |
} | ||
}; | ||
|
||
/** | ||
* Parse an inline-declaration found in the markdown input | ||
* | ||
* @param {string} declaration | ||
*/ | ||
const parseLinterDeclaration = declaration => { | ||
// Trim off any excess spaces from the beginning & end | ||
declaration = declaration.trim(); | ||
|
||
// Extract the name for the declaration | ||
const [name, ...value] = declaration.split(' '); | ||
|
||
switch (name) { | ||
case 'skip-deprecation': { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wdyt of defining these definitions similarly to the rules & reporters? (i.e. have them defined in a |
||
if (value.length !== 1) { | ||
issues.push({ | ||
level: 'error', | ||
location: { | ||
// TODO, | ||
path: '', | ||
position: 0, | ||
}, | ||
message: LINT_MESSAGES.malformedLinterDeclaration.replace( | ||
'{{message}}', | ||
`Expected 1 argument, got ${value.length}` | ||
), | ||
}); | ||
|
||
break; | ||
} | ||
|
||
// Get the deprecation code. This should be something like DEP0001. | ||
const deprecation = value[0]; | ||
|
||
// Extract the number from the code | ||
const deprecationCode = Number(deprecation.substring('DEP'.length)); | ||
|
||
// Make sure this is a valid deprecation code, output an error otherwise | ||
if ( | ||
deprecation.length !== 7 || | ||
!deprecation.startsWith('DEP') || | ||
isNaN(deprecationCode) | ||
) { | ||
issues.push({ | ||
level: 'error', | ||
location: { | ||
// TODO, | ||
path: '', | ||
position: 0, | ||
}, | ||
message: LINT_MESSAGES.malformedLinterDeclaration.replace( | ||
'{{message}}', | ||
`Invalid deprecation code ${deprecation}` | ||
), | ||
}); | ||
|
||
break; | ||
} | ||
|
||
declarations.skipDeprecation.push(deprecationCode); | ||
|
||
break; | ||
} | ||
default: { | ||
issues.push({ | ||
level: 'error', | ||
location: { | ||
// TODO | ||
path: '', | ||
position: 0, | ||
}, | ||
message: LINT_MESSAGES.invalidLinterDeclaration.replace( | ||
'{{declaration}}', | ||
name | ||
), | ||
}); | ||
break; | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Checks if any error-level issues were found during linting | ||
* | ||
|
@@ -70,6 +159,7 @@ const createLinter = (dryRun, disabledRules) => { | |
return { | ||
lintAll, | ||
report, | ||
parseLinterDeclaration, | ||
hasError, | ||
}; | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
'use strict'; | ||
|
||
import { LINT_MESSAGES } from '../../constants.mjs'; | ||
import { buildHierarchy } from '../../utils/buildHierarchy.mjs'; | ||
import { DEPRECATION_HEADER_REGEX } from '../constants.mjs'; | ||
import getDeprecationEntries from './utils/getDeprecationEntries.mjs'; | ||
|
||
/** | ||
* @param {ApiDocMetadataEntry} deprecation | ||
* @param {number} expectedCode | ||
* @returns {Array<import('../types').LintIssue>} | ||
*/ | ||
function lintDeprecation(deprecation, expectedCode) { | ||
// Try validating the header (`DEPXXXX: ...`) and extract the code for us to | ||
// look at | ||
const match = deprecation.heading.data.text.match(DEPRECATION_HEADER_REGEX); | ||
|
||
if (!match) { | ||
// Malformed header | ||
return [ | ||
{ | ||
level: 'error', | ||
location: { | ||
path: deprecation.api_doc_source, | ||
position: deprecation.yaml_position, | ||
}, | ||
message: LINT_MESSAGES.malformedDeprecationHeader, | ||
}, | ||
]; | ||
} | ||
|
||
const code = Number(match[1]); | ||
|
||
return code === expectedCode | ||
? [] | ||
: [ | ||
{ | ||
level: 'error', | ||
location: { | ||
path: deprecation.api_doc_source, | ||
position: deprecation.yaml_position, | ||
}, | ||
message: LINT_MESSAGES.outOfOrderDeprecationCode | ||
.replaceAll('{{code}}', match[1]) | ||
.replace('{{expectedCode}}', `${expectedCode}`.padStart(4, '0')), | ||
}, | ||
]; | ||
} | ||
|
||
/** | ||
* Checks if any deprecation codes are out of order | ||
* | ||
* @type {import('../types').LintRule} | ||
*/ | ||
export const deprecationCodeOrder = (entries, declarations) => { | ||
if (entries.length === 0 || entries[0].api !== 'deprecations') { | ||
// This is only relevant to doc/api/deprecations.md | ||
return []; | ||
} | ||
|
||
const issues = []; | ||
|
||
const hierarchy = buildHierarchy(entries); | ||
|
||
hierarchy.forEach(root => { | ||
const deprecations = getDeprecationEntries(root.hierarchyChildren); | ||
|
||
let expectedCode = 1; | ||
|
||
for (const deprecation of deprecations || []) { | ||
while (declarations.skipDeprecation.includes(expectedCode)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably a nicer way to do this loop |
||
expectedCode++; | ||
} | ||
|
||
issues.push(...lintDeprecation(deprecation, expectedCode)); | ||
|
||
expectedCode++; | ||
} | ||
}); | ||
|
||
return issues; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use strict'; | ||
|
||
import { LINT_MESSAGES } from '../../constants.mjs'; | ||
import { valid } from 'semver'; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use strict'; | ||
|
||
/** | ||
* Checks if any change version is missing | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use strict'; | ||
|
||
import { LINT_MESSAGES } from '../../constants.mjs'; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
'use strict'; | ||
|
||
/** | ||
* @param {Array<import('../../../generators/legacy-json/types').HierarchizedEntry>} hierarchy | ||
* @returns {Array<import('../../../generators/legacy-json/types').HierarchizedEntry> | undefined} | ||
*/ | ||
export default function getDeprecationEntries(hierarchy) { | ||
for (const child of hierarchy) { | ||
if (child.slug === 'list-of-deprecated-apis') { | ||
return child.hierarchyChildren; | ||
} | ||
} | ||
|
||
return undefined; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The declarations kinda feel retrofitted in but not sure if there's a better way to pass them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think declarations could be part of metadata entries