js-ast-utils: Add getCompletionRecords
ADD
packages/@romejs/js-ast-utils/__rtests__/__rsnapshots__/getCompletionRecords.ts.rsnap
+{
+ "invalid": "Array [\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n body: Array []\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 17\n index: 17\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n }\n }\n]",
+ "invalid: 1": "Array [\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n body: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 25\n index: 25\n line: 1\n }\n start: Object {\n column: 14\n index: 14\n line: 1\n }\n }\n directives: Array [\n Directive {\n value: 'foobar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 24\n index: 24\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n }\n ]\n }\n }\n]",
+ "invalid: 2": "Array [\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 35\n index: 35\n line: 1\n }\n start: Object {\n column: 24\n index: 24\n line: 1\n }\n }\n body: Array [\n ExpressionStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 34\n index: 34\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n expression: StringLiteral {\n value: 'foobar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 33\n index: 33\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n }\n }\n ]\n }\n }\n INVALID {\n description: 'empty alternate'\n node: IfStatement {\n alternate: undefined\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 35\n index: 35\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n test: ReferenceIdentifier {\n name: 'bar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 22\n index: 22\n line: 1\n }\n start: Object {\n column: 19\n index: 19\n line: 1\n }\n }\n }\n consequent: BlockStatement {\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 35\n index: 35\n line: 1\n }\n start: Object {\n column: 24\n index: 24\n line: 1\n }\n }\n body: Array [\n ExpressionStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 34\n index: 34\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n expression: StringLiteral {\n value: 'foobar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 33\n index: 33\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n }\n }\n ]\n }\n }\n }\n]",
+ "invalid: 3": "Array [\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 35\n index: 35\n line: 1\n }\n start: Object {\n column: 24\n index: 24\n line: 1\n }\n }\n body: Array [\n ExpressionStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 34\n index: 34\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n expression: StringLiteral {\n value: 'foobar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 33\n index: 33\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n }\n }\n ]\n }\n }\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n body: Array []\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 43\n index: 43\n line: 1\n }\n start: Object {\n column: 41\n index: 41\n line: 1\n }\n }\n }\n }\n]",
+ "invalid: 4": "Array [\n INVALID {\n description: 'switch with no default clause'\n node: SwitchStatement {\n cases: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 30\n index: 30\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n discriminant: ReferenceIdentifier {\n name: 'foo'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 26\n index: 26\n line: 1\n }\n start: Object {\n column: 23\n index: 23\n line: 1\n }\n }\n }\n }\n }\n]",
+ "invalid: 5": "Array [\n INVALID {\n description: 'switch with no default clause'\n node: SwitchStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 44\n index: 44\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n discriminant: ReferenceIdentifier {\n name: 'foo'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 26\n index: 26\n line: 1\n }\n start: Object {\n column: 23\n index: 23\n line: 1\n }\n }\n }\n cases: Array [\n SwitchCase {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 43\n index: 43\n line: 1\n }\n start: Object {\n column: 39\n index: 39\n line: 1\n }\n }\n test: StringLiteral {\n value: 'bar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 39\n index: 39\n line: 1\n }\n start: Object {\n column: 34\n index: 34\n line: 1\n }\n }\n }\n consequent: Array [\n BlockStatement {\n body: Array []\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 43\n index: 43\n line: 1\n }\n start: Object {\n column: 41\n index: 41\n line: 1\n }\n }\n }\n ]\n }\n ]\n }\n }\n]",
+ "invalid: 6": "Array [\n INVALID {\n description: 'empty block'\n node: BlockStatement {\n body: Array []\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 40\n index: 40\n line: 1\n }\n start: Object {\n column: 38\n index: 38\n line: 1\n }\n }\n }\n }\n]",
+ "completions": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 28\n index: 28\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 27\n index: 27\n line: 1\n }\n start: Object {\n column: 22\n index: 22\n line: 1\n }\n }\n }\n }\n }\n]",
+ "completions: 1": "Array [\n COMPLETION {\n node: ReturnStatement {\n argument: undefined\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 22\n index: 22\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n }\n }\n]",
+ "completions: 2": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 38\n index: 38\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 37\n index: 37\n line: 1\n }\n start: Object {\n column: 32\n index: 32\n line: 1\n }\n }\n }\n }\n }\n INVALID {\n description: 'empty alternate'\n node: IfStatement {\n alternate: undefined\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 39\n index: 39\n line: 1\n }\n start: Object {\n column: 15\n index: 15\n line: 1\n }\n }\n test: ReferenceIdentifier {\n name: 'bar'\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 22\n index: 22\n line: 1\n }\n start: Object {\n column: 19\n index: 19\n line: 1\n }\n }\n }\n consequent: BlockStatement {\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 39\n index: 39\n line: 1\n }\n start: Object {\n column: 24\n index: 24\n line: 1\n }\n }\n body: Array [\n ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 38\n index: 38\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 37\n index: 37\n line: 1\n }\n start: Object {\n column: 32\n index: 32\n line: 1\n }\n }\n }\n }\n ]\n }\n }\n }\n]",
+ "completions: 3": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 38\n index: 38\n line: 1\n }\n start: Object {\n column: 25\n index: 25\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 37\n index: 37\n line: 1\n }\n start: Object {\n column: 32\n index: 32\n line: 1\n }\n }\n }\n }\n }\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 58\n index: 58\n line: 1\n }\n start: Object {\n column: 46\n index: 46\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: true\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 57\n index: 57\n line: 1\n }\n start: Object {\n column: 53\n index: 53\n line: 1\n }\n }\n }\n }\n }\n]",
+ "completions: 4": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 52\n index: 52\n line: 1\n }\n start: Object {\n column: 39\n index: 39\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 51\n index: 51\n line: 1\n }\n start: Object {\n column: 46\n index: 46\n line: 1\n }\n }\n }\n }\n }\n]",
+ "completions: 5": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 52\n index: 52\n line: 1\n }\n start: Object {\n column: 39\n index: 39\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 51\n index: 51\n line: 1\n }\n start: Object {\n column: 46\n index: 46\n line: 1\n }\n }\n }\n }\n }\n]",
+ "mix": "Array [\n COMPLETION {\n node: ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 63\n index: 63\n line: 1\n }\n start: Object {\n column: 50\n index: 50\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 62\n index: 62\n line: 1\n }\n start: Object {\n column: 57\n index: 57\n line: 1\n }\n }\n }\n }\n }\n INVALID {\n description: 'empty alternate'\n node: IfStatement {\n alternate: undefined\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 64\n index: 64\n line: 1\n }\n start: Object {\n column: 39\n index: 39\n line: 1\n }\n }\n test: BooleanLiteral {\n value: true\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 47\n index: 47\n line: 1\n }\n start: Object {\n column: 43\n index: 43\n line: 1\n }\n }\n }\n consequent: BlockStatement {\n directives: Array []\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 64\n index: 64\n line: 1\n }\n start: Object {\n column: 49\n index: 49\n line: 1\n }\n }\n body: Array [\n ReturnStatement {\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 63\n index: 63\n line: 1\n }\n start: Object {\n column: 50\n index: 50\n line: 1\n }\n }\n argument: BooleanLiteral {\n value: false\n loc: Object {\n filename: 'unknown'\n end: Object {\n column: 62\n index: 62\n line: 1\n }\n start: Object {\n column: 57\n index: 57\n line: 1\n }\n }\n }\n }\n ]\n }\n }\n }\n]"
+}
\ No newline at end of file

ADD
packages/@romejs/js-ast-utils/__rtests__/getCompletionRecords.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import getCompletionRecords from '../getCompletionRecords';
+import test from '@romejs/test';
+import {parseJS} from '@romejs/js-parser';
+import {functionDeclaration} from '@romejs/js-ast';
+
+function helper(input: string) {
+ return getCompletionRecords(
+ functionDeclaration.assert(
+ parseJS({
+ path: 'unknown',
+ input: `function foo(){${input}}`,
+ }).body[0],
+ ).body,
+ );
+}
+
+test('invalid', t => {
+ t.snapshot(helper(`{}`));
+ t.snapshot(helper(`'foobar';`));
+ t.snapshot(helper(`if (bar) {'foobar';}`));
+ t.snapshot(helper(`if (bar) {'foobar';} else {}`));
+ t.snapshot(helper(`switch (foo) {}`));
+ t.snapshot(helper(`switch (foo) {case 'bar': {}}`));
+ t.snapshot(helper(`switch (foo) {default: {}}`));
+});
+
+test('completions', t => {
+ t.snapshot(helper(`return false;`));
+ t.snapshot(helper(`return; invalid;`));
+ t.snapshot(helper(`if (bar) {return false;}`));
+ t.snapshot(helper(`if (bar) {return false;} else {return true;}`));
+ t.snapshot(helper(`switch (foo) {default: {return false;}}`));
+ t.snapshot(helper(`switch (foo) {default: {return false;}}`));
+});
+
+test('mix', t => {
+ t.snapshot(helper(`switch (foo) {default: {if (true) {return false;}}}`));
+});

ADD
packages/@romejs/js-ast-utils/getCompletionRecords.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {
+ AnyNode,
+ ReturnStatement,
+ ContinueStatement,
+ BreakStatement,
+ ThrowStatement,
+ AnyStatement,
+} from '@romejs/js-ast';
+
+type CompletionRecord = {
+ type: 'COMPLETION';
+ node: ReturnStatement | ContinueStatement | BreakStatement | ThrowStatement;
+};
+
+type InvalidRecord = {
+ type: 'INVALID';
+ description: string;
+ node: AnyNode;
+};
+
+type Records = Array<CompletionRecord | InvalidRecord>;
+
+function getIfCompletionRecords(
+ node: undefined | AnyNode,
+ parent: AnyNode,
+ key: string,
+): Records {
+ if (node === undefined) {
+ return [
+ {
+ type: 'INVALID',
+ description: `empty ${key}`,
+ node: parent,
+ },
+ ];
+ } else {
+ return getCompletionRecords(node);
+ }
+}
+
+function getLastCompletionRecordFromNodes(
+ nodes: Array<AnyStatement>,
+): undefined | Records {
+ // Get the last node to produce records
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ const node = nodes[i];
+ const records = _getCompletionRecords(node);
+ if (records !== undefined) {
+ return records;
+ }
+ }
+}
+
+function _getCompletionRecords(node: AnyNode): undefined | Records {
+ if (node.type === 'BlockStatement') {
+ const records = getLastCompletionRecordFromNodes(node.body);
+ if (records !== undefined) {
+ return records;
+ }
+
+ return [
+ {
+ type: 'INVALID',
+ description: 'empty block',
+ node,
+ },
+ ];
+ }
+
+ if (node.type === 'SwitchStatement') {
+ for (const caseNode of node.cases) {
+ if (caseNode.test === undefined) {
+ const records = getLastCompletionRecordFromNodes(caseNode.consequent);
+ if (records === undefined) {
+ return [
+ {
+ type: 'INVALID',
+ description: 'default switch clause with no completions',
+ node: caseNode,
+ },
+ ];
+ } else {
+ return records;
+ }
+ }
+ }
+
+ return [
+ {
+ type: 'INVALID',
+ description: 'switch with no default clause',
+ node,
+ },
+ ];
+ }
+
+ if (node.type === 'IfStatement') {
+ return [
+ ...getIfCompletionRecords(node.consequent, node, 'consequent'),
+ ...getIfCompletionRecords(node.alternate, node, 'alternate'),
+ ];
+ }
+
+ if (
+ node.type === 'ReturnStatement' ||
+ node.type === 'ContinueStatement' ||
+ node.type === 'BreakStatement' ||
+ node.type === 'ThrowStatement'
+ ) {
+ return [
+ {
+ type: 'COMPLETION',
+ node,
+ },
+ ];
+ }
+}
+
+export default function getCompletionRecords(node: AnyNode): Records {
+ const records = _getCompletionRecords(node);
+ if (records === undefined) {
+ return [
+ {
+ type: 'INVALID',
+ description: 'invalid node',
+ node,
+ },
+ ];
+ } else {
+ return records;
+ }
+}

MODIFY
packages/@romejs/js-ast-utils/index.ts
packages/@romejs/js-ast-utils/index.ts
@@ -30,3 +30,4 @@ export {default as getRequireSource} from './getRequireSource';
export {default as isIdentifierish} from './isIdentifierish';
export {default as isVariableIdentifier} from './isVariableIdentifier';
export {default as isTypeExpressionWrapperNode} from './isTypeExpressionWrapperNode';
+export {default as getCompletionRecords} from './getCompletionRecords';

MODIFY
packages/@romejs/js-ast/statements/BlockStatement.ts
packages/@romejs/js-ast/statements/BlockStatement.ts
*/
import {JSNodeBase, AnyStatement, Directive} from '../index';
-import {createBuilder} from '../utils';
+import {createQuickBuilder} from '../utils';
export type BlockStatement = JSNodeBase & {
type: 'BlockStatement';
@@ -14,10 +14,14 @@ export type BlockStatement = JSNodeBase & {
directives?: Array<Directive>;
};
-export const blockStatement = createBuilder<BlockStatement>('BlockStatement', {
+export const blockStatement = createQuickBuilder<BlockStatement, 'body'>(
+ 'BlockStatement',
+ 'body',
+ {
bindingKeys: {},
visitorKeys: {
body: true,
directives: true,
},
-});
+ },
+);

js-ast-utils: Add getCompletionRecords