112 lines
2.9 KiB
JavaScript
112 lines
2.9 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*
|
||
|
*
|
||
|
* @format
|
||
|
*/
|
||
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.getModuleDocblock = getModuleDocblock;
|
||
|
exports.parseDocblockString = parseDocblockString;
|
||
|
const DIRECTIVE_REGEX = /^\s*@([a-zA-Z0-9_-]+)( +.+)?$/;
|
||
|
|
||
|
function parseDocblockString(docblock) {
|
||
|
const directiveLines = docblock.split('\n') // remove the leading " *" from each line
|
||
|
.map(line => line.trimStart().replace(/^\* ?/, '').trim()).filter(line => line.startsWith('@'));
|
||
|
const directives = // $FlowExpectedError[incompatible-type] - flow types this return as {...}
|
||
|
Object.create(null);
|
||
|
|
||
|
for (const line of directiveLines) {
|
||
|
var _match$;
|
||
|
|
||
|
const match = DIRECTIVE_REGEX.exec(line);
|
||
|
|
||
|
if (match == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const name = match[1]; // explicitly use an empty string if there's no value
|
||
|
// this way the array length tracks how many instances of the directive there was
|
||
|
|
||
|
const value = ((_match$ = match[2]) != null ? _match$ : '').trim();
|
||
|
|
||
|
if (directives[name]) {
|
||
|
directives[name].push(value);
|
||
|
} else {
|
||
|
directives[name] = [value];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return directives;
|
||
|
}
|
||
|
|
||
|
function getModuleDocblock(hermesProgram) {
|
||
|
const docblockNode = (() => {
|
||
|
if (hermesProgram.type !== 'Program') {
|
||
|
return null;
|
||
|
} // $FlowExpectedError[incompatible-type] - escape out of the unsafe hermes types
|
||
|
|
||
|
|
||
|
const program = hermesProgram;
|
||
|
|
||
|
if (program.comments.length === 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const firstComment = (() => {
|
||
|
const first = program.comments[0];
|
||
|
|
||
|
if (first.type === 'Block') {
|
||
|
return first;
|
||
|
}
|
||
|
|
||
|
if (program.comments.length === 1) {
|
||
|
return null;
|
||
|
} // ESLint will always strip out the shebang comment from the code before passing it to the parser
|
||
|
// https://github.com/eslint/eslint/blob/21d647904dc30f9484b22acdd9243a6d0ecfba38/lib/linter/linter.js#L779
|
||
|
// this means that we're forced to parse it as a line comment :(
|
||
|
// this hacks around it by selecting the second comment in this case
|
||
|
|
||
|
|
||
|
const second = program.comments[1];
|
||
|
|
||
|
if (first.type === 'Line' && first.range[0] === 0 && second.type === 'Block') {
|
||
|
return second;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
})();
|
||
|
|
||
|
if (firstComment == null) {
|
||
|
return null;
|
||
|
}
|
||
|
/*
|
||
|
Handle cases like this where the comment isn't actually the first thing in the code:
|
||
|
```
|
||
|
const x = 1; /* docblock *./
|
||
|
```
|
||
|
*/
|
||
|
|
||
|
|
||
|
if (program.body.length > 0 && program.body[0].range[0] < firstComment.range[0]) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return firstComment;
|
||
|
})();
|
||
|
|
||
|
if (docblockNode == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
directives: parseDocblockString(docblockNode.value),
|
||
|
comment: docblockNode
|
||
|
};
|
||
|
}
|