Skip to content

Commit 9edf677

Browse files
committed
feat: Programmatic building of type-checkable JS and declaration files
Also: - feat(`lib/espree`): throws specific error if `jsx_readString` superclass undefined - refactor(`lib/espree`): changes to force EspreeParser constructor to convert string as created with `new String` to plain string (for typing) - refactor(`lib/espree`): checks for existence of `firstNode.range` and `firstNode.loc` in `parse` (as may be absent) - refactor(`lib/espree`): checks for existence of `tokens` in `tokenize` (as may be absent) - refactor(`lib/espree`): checks for existence of `extra.lastToken.range` and `extra.lastToken.loc` in `parse` (as may be absent) - refactor(`lib/token-translator``): checks for existence of `lastTemplateToken.loc` and `lastTemplateToken.range` (as may be absent) - chore: update devDeps.
1 parent e5982ef commit 9edf677

11 files changed

+1018
-301
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/tests/fixtures
33
/dist
44
tools/create-test-example.js
5+
tmp

.eslintrc.cjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module.exports = {
1212
}
1313
},
1414
parserOptions: {
15-
ecmaVersion: 2020,
15+
ecmaVersion: 2022,
1616
sourceType: "module"
1717
},
1818
overrides: [
@@ -34,5 +34,10 @@ module.exports = {
3434
"no-console": "off"
3535
}
3636
}
37-
]
37+
],
38+
rules: {
39+
"jsdoc/check-tag-names": ["error", {
40+
definedTags: ["local", "export"]
41+
}]
42+
}
3843
};

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ _test.js
1010
.eslint-release-info.json
1111
yarn.lock
1212
package-lock.json
13+
tmp

espree.js

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,57 @@
5656
*/
5757
/* eslint no-undefined:0, no-use-before-define: 0 */
5858

59+
// ----------------------------------------------------------------------------
60+
// Types exported from file
61+
// ----------------------------------------------------------------------------
62+
/**
63+
* `jsx.Options` gives us 2 optional properties, so extend it
64+
*
65+
* `allowReserved`, `ranges`, `locations`, `allowReturnOutsideFunction`,
66+
* `onToken`, and `onComment` are as in `acorn.Options`
67+
*
68+
* `ecmaVersion` as in `acorn.Options` though optional
69+
*
70+
* `sourceType` as in `acorn.Options` but also allows `commonjs`
71+
*
72+
* `ecmaFeatures`, `range`, `loc`, `tokens` are not in `acorn.Options`
73+
*
74+
* `comment` is not in `acorn.Options` and doesn't err without it, but is used
75+
* @typedef {{
76+
* allowReserved?: boolean | "never",
77+
* ranges?: boolean,
78+
* locations?: boolean,
79+
* allowReturnOutsideFunction?: boolean,
80+
* onToken?: ((token: acorn.Token) => any) | acorn.Token[],
81+
* onComment?: ((
82+
* isBlock: boolean, text: string, start: number, end: number, startLoc?: acorn.Position,
83+
* endLoc?: acorn.Position
84+
* ) => void) | acorn.Comment[],
85+
* ecmaVersion?: acorn.ecmaVersion,
86+
* sourceType?: "script"|"module"|"commonjs",
87+
* ecmaFeatures?: {
88+
* jsx?: boolean,
89+
* globalReturn?: boolean,
90+
* impliedStrict?: boolean
91+
* },
92+
* range?: boolean,
93+
* loc?: boolean,
94+
* tokens?: boolean | null,
95+
* comment?: boolean,
96+
* } & jsx.Options} ParserOptions
97+
*/
98+
99+
// ----------------------------------------------------------------------------
100+
// Local type imports
101+
// ----------------------------------------------------------------------------
102+
/**
103+
* @local
104+
* @typedef {import('acorn')} acorn
105+
* @typedef {typeof import('acorn-jsx').AcornJsxParser} AcornJsxParser
106+
* @typedef {import('./lib/espree').EnhancedSyntaxError} EnhancedSyntaxError
107+
* @typedef {typeof import('./lib/espree').EspreeParser} IEspreeParser
108+
*/
109+
59110
import * as acorn from "acorn";
60111
import jsx from "acorn-jsx";
61112
import espree from "./lib/espree.js";
@@ -66,23 +117,43 @@ import { getLatestEcmaVersion, getSupportedEcmaVersions } from "./lib/options.js
66117

67118
// To initialize lazily.
68119
const parsers = {
69-
_regular: null,
70-
_jsx: null,
120+
_regular: /** @type {IEspreeParser|null} */ (null),
121+
_jsx: /** @type {IEspreeParser|null} */ (null),
71122

123+
/**
124+
* Returns regular Parser
125+
* @returns {IEspreeParser} Regular Acorn parser
126+
*/
72127
get regular() {
73128
if (this._regular === null) {
74-
this._regular = acorn.Parser.extend(espree());
129+
const espreeParserFactory = espree();
130+
131+
// Cast the `acorn.Parser` to our own for required properties not specified in *.d.ts
132+
this._regular = espreeParserFactory(/** @type {AcornJsxParser} */ (acorn.Parser));
75133
}
76134
return this._regular;
77135
},
78136

137+
/**
138+
* Returns JSX Parser
139+
* @returns {IEspreeParser} JSX Acorn parser
140+
*/
79141
get jsx() {
80142
if (this._jsx === null) {
81-
this._jsx = acorn.Parser.extend(jsx(), espree());
143+
const espreeParserFactory = espree();
144+
const jsxFactory = jsx();
145+
146+
// Cast the `acorn.Parser` to our own for required properties not specified in *.d.ts
147+
this._jsx = espreeParserFactory(jsxFactory(acorn.Parser));
82148
}
83149
return this._jsx;
84150
},
85151

152+
/**
153+
* Returns Regular or JSX Parser
154+
* @param {ParserOptions} options Parser options
155+
* @returns {IEspreeParser} Regular or JSX Acorn parser
156+
*/
86157
get(options) {
87158
const useJsx = Boolean(
88159
options &&
@@ -101,9 +172,9 @@ const parsers = {
101172
/**
102173
* Tokenizes the given code.
103174
* @param {string} code The code to tokenize.
104-
* @param {Object} options Options defining how to tokenize.
105-
* @returns {Token[]} An array of tokens.
106-
* @throws {SyntaxError} If the input code is invalid.
175+
* @param {ParserOptions} options Options defining how to tokenize.
176+
* @returns {acorn.Token[]|null} An array of tokens.
177+
* @throws {EnhancedSyntaxError} If the input code is invalid.
107178
* @private
108179
*/
109180
export function tokenize(code, options) {
@@ -124,9 +195,9 @@ export function tokenize(code, options) {
124195
/**
125196
* Parses the given code.
126197
* @param {string} code The code to tokenize.
127-
* @param {Object} options Options defining how to tokenize.
128-
* @returns {ASTNode} The "Program" AST node.
129-
* @throws {SyntaxError} If the input code is invalid.
198+
* @param {ParserOptions} options Options defining how to tokenize.
199+
* @returns {acorn.Node} The "Program" AST node.
200+
* @throws {EnhancedSyntaxError} If the input code is invalid.
130201
*/
131202
export function parse(code, options) {
132203
const Parser = parsers.get(options);
@@ -148,17 +219,15 @@ export const VisitorKeys = (function() {
148219
// Derive node types from VisitorKeys
149220
/* istanbul ignore next */
150221
export const Syntax = (function() {
151-
let name,
222+
let /** @type {Object<string,string>} */
152223
types = {};
153224

154225
if (typeof Object.create === "function") {
155226
types = Object.create(null);
156227
}
157228

158-
for (name in VisitorKeys) {
159-
if (Object.hasOwnProperty.call(VisitorKeys, name)) {
160-
types[name] = name;
161-
}
229+
for (const name of Object.keys(VisitorKeys)) {
230+
types[name] = name;
162231
}
163232

164233
if (typeof Object.freeze === "function") {

0 commit comments

Comments
 (0)