js-ast-utils: Add getNodeReferenceParts and doesNodeMatchPattern tests and improvements
ADD
packages/@romejs/js-ast-utils/doesNodeMatchPattern.test.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 {test} from 'rome';
+import doesNodeMatchPattern from './doesNodeMatchPattern';
+import template from './template';
+
+test(
+ 'doesNodeMatchPattern',
+ (t) => {
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`foo`, 'foo'),
+ 'true',
+ );
+
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`this.foo`, 'this.foo'),
+ 'true',
+ );
+
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`exports.foo`, 'exports.**'),
+ 'true',
+ );
+
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`this.foo.bar`, 'this.foo.*'),
+ 'true',
+ );
+
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`this.foo.bar.yes`, 'this.foo.*'),
+ 'false',
+ );
+
+ t.inlineSnapshot(
+ doesNodeMatchPattern(template.expression`this.foo.bar.yes`, 'this.foo.**'),
+ 'true',
+ );
+ },
+);

MODIFY
packages/@romejs/js-ast-utils/doesNodeMatchPattern.ts
packages/@romejs/js-ast-utils/doesNodeMatchPattern.ts
import {AnyNode} from '@romejs/js-ast';
import getNodeReferenceParts from './getNodeReferenceParts';
+import isIdentifierish from './isIdentifierish';
+
+const splitCache: Map<string, SplitResult> = new Map();
+
+type SplitResult = {
+ hasDoubleStar: boolean;
+ parts: Array<string>;
+};
+
+function split(str: string): SplitResult {
+ const cached = splitCache.get(str);
+ if (cached !== undefined) {
+ return cached;
+ }
+
+ const parts = str.split('.');
+
+ let hasDoubleStar = false;
+ for (const part of parts) {
+ if (part === '**') {
+ hasDoubleStar = true;
+ break;
+ }
+ }
+
+ const result: SplitResult = {parts, hasDoubleStar};
+ splitCache.set(str, result);
+ return result;
+}
-// TODO make this accept multiple matches
export default function doesNodeMatchPattern(
- member: AnyNode,
- match: string | Array<string>,
- allowPartial: boolean = false,
+ node: undefined | AnyNode,
+ match: string,
): boolean {
+ if (node === undefined) {
+ return false;
+ }
+
// Not a member expression
- if (member.type !== 'MemberExpression' && member.type !== 'Identifier') {
+ if (node.type !== 'MemberExpression' && !isIdentifierish(node)) {
return false;
}
- const expectedParts: Array<string> = Array.isArray(match)
- ? match.slice()
- : match.split('.');
+ const {parts: expectedParts, hasDoubleStar} = split(match);
- const {bailed, parts: actualParts} = getNodeReferenceParts(member);
+ const {bailed, parts: actualParts} = getNodeReferenceParts(node);
// Bailed will be true if we were unable to derive a name for one of the parts
- if (bailed && !allowPartial) {
+ if (bailed && !hasDoubleStar) {
return false;
}
@@ -35,29 +64,40 @@ export default function doesNodeMatchPattern(
return false;
}
- // I there's more parts than we expect and we weren't passed the allowPartial flag then it's never going to match either
- if (allowPartial === false && actualParts.length > expectedParts.length) {
+ // I there's more parts than we expect then it's never going to match either
+ if (!hasDoubleStar && actualParts.length > expectedParts.length) {
return false;
}
+ let nextActualIndex = 0;
+ let nextExpectedIndex = 0;
+
// Loop over the parts we received and match them
- while (actualParts.length > 0) {
- // If we have no more expected parts then return based on if we allow partial matches
- if (expectedParts.length === 0) {
- return allowPartial;
+ while (nextActualIndex < actualParts.length) {
+ // If we have no more expected parts then we can't possibly match it
+ if (nextActualIndex >= expectedParts.length) {
+ return false;
}
- const actual = actualParts.shift()!.value;
- const expected = expectedParts.shift();
+ const actual = actualParts[nextActualIndex].value;
+ nextActualIndex++;
+
+ const expected = expectedParts[nextExpectedIndex];
+ nextExpectedIndex++;
// A star part can accept anything
if (expected === '*') {
continue;
}
- // A double star will eat as many parts from 'actual until we find the next expected part
if (expected === '**') {
- const next = expectedParts.shift();
+ // Ran out of matches but we've accepted the current part
+ if (nextExpectedIndex >= expectedParts.length) {
+ return true;
+ }
+
+ const next = expectedParts[nextExpectedIndex];
+ nextExpectedIndex++;
if (next === '*' || next === '**') {
throw new Error(
@@ -67,8 +107,10 @@ export default function doesNodeMatchPattern(
let found = false;
- while (actualParts.length > 0) {
- const actual = actualParts.shift();
+ // Eat as many parts until we find the next expected part
+ while (nextActualIndex < actualParts.length) {
+ const actual = actualParts[nextActualIndex].value;
+ nextActualIndex++;
if (actual === next) {
found = true;
break;

ADD
packages/@romejs/js-ast-utils/getNodeReferenceParts.test.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 {test} from 'rome';
+import getNodeReferenceParts from './getNodeReferenceParts';
+import template from './template';
+
+test(
+ 'getNodeReferenceParts',
+ (t) => {
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`foo`),
+ "Object {\n bailed: false\n parts: Array [\n Object {\n value: 'foo'\n node: ReferenceIdentifier {name: 'foo'}\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`foo.bar`),
+ "Object {\n bailed: false\n parts: Array [\n Object {\n value: 'foo'\n node: ReferenceIdentifier {name: 'foo'}\n }\n Object {\n value: 'bar'\n node: Identifier {name: 'bar'}\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`this.bar`),
+ "Object {\n bailed: false\n parts: Array [\n Object {\n value: 'this'\n node: ThisExpression {}\n }\n Object {\n value: 'bar'\n node: Identifier {name: 'bar'}\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`this.bar[bar]`),
+ "Object {\n bailed: true\n parts: Array [\n Object {\n value: 'this'\n node: ThisExpression {}\n }\n Object {\n value: 'bar'\n node: Identifier {name: 'bar'}\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`import.meta`),
+ "Object {\n bailed: false\n parts: Array [\n Object {\n value: 'import'\n node: MetaProperty {\n meta: Identifier {name: 'import'}\n property: Identifier {name: 'meta'}\n }\n }\n Object {\n value: 'meta'\n node: MetaProperty {\n meta: Identifier {name: 'import'}\n property: Identifier {name: 'meta'}\n }\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`foo['bar']`),
+ "Object {\n bailed: false\n parts: Array [\n Object {\n value: 'foo'\n node: ReferenceIdentifier {name: 'foo'}\n }\n Object {\n value: 'bar'\n node: StringLiteral {value: 'bar'}\n }\n ]\n}",
+ );
+ t.inlineSnapshot(
+ getNodeReferenceParts(template.expression`foo[bar]`),
+ "Object {\n bailed: true\n parts: Array [\n Object {\n value: 'foo'\n node: ReferenceIdentifier {name: 'foo'}\n }\n ]\n}",
+ );
+ },
+);

MODIFY
packages/@romejs/js-ast-utils/getNodeReferenceParts.ts
packages/@romejs/js-ast-utils/getNodeReferenceParts.ts
*/
import {AnyNode} from '@romejs/js-ast';
+import isIdentifierish from './isIdentifierish';
type Parts = Array<{
value: string;
node: AnyNode;
}>;
-export default function getNodeReferenceParts(
- node: AnyNode,
-): {
+type Result = {
bailed: boolean;
parts: Parts;
-} {
+};
+
+const cache: WeakMap<AnyNode, Result> = new WeakMap();
+
+const EMPTY: Result = {
+ bailed: true,
+ parts: [],
+};
+
+export default function getNodeReferenceParts(node: undefined | AnyNode): Result {
+ if (node === undefined) {
+ return EMPTY;
+ }
+
+ const cached = cache.get(node);
+ if (cached !== undefined) {
+ return cached;
+ }
+
const parts: Parts = [];
function add(node: AnyNode): boolean {
- if (node.type === 'Identifier' || node.type === 'ReferenceIdentifier') {
+ if (isIdentifierish(node)) {
parts.push({node, value: node.name});
return false;
+ } else if (node.type === 'ThisExpression') {
+ parts.push({node, value: 'this'});
+ return false;
} else if (node.type === 'StringLiteral') {
parts.push({node, value: node.value});
return false;
@@ -51,6 +71,7 @@ export default function getNodeReferenceParts(
}
const bailed = add(node);
-
- return {bailed, parts};
+ const result: Result = {bailed, parts};
+ cache.set(node, result);
+ return result;
}

MODIFY
packages/@romejs/js-ast-utils/getRequireSource.ts
packages/@romejs/js-ast-utils/getRequireSource.ts
@@ -9,9 +9,6 @@ import {Scope} from '@romejs/js-compiler';
import {AnyNode} from '@romejs/js-ast';
import doesNodeMatchPattern from './doesNodeMatchPattern';
-const ROME_DEFAULT_REQUIRE = ['Rome', 'requireDefault'];
-const ROME_NAMESPACE_REQUIRE = ['Rome', 'requireNamespace'];
-
export default function getRequireSource(
node: undefined | AnyNode,
scope: Scope,
@@ -46,8 +43,8 @@ export default function getRequireSource(
scope.getBinding('require') === undefined;
const validRomeRequreCallee =
- (doesNodeMatchPattern(callee, ROME_DEFAULT_REQUIRE) ||
- doesNodeMatchPattern(callee, ROME_NAMESPACE_REQUIRE)) &&
+ (doesNodeMatchPattern(callee, 'Rome.requireDefault') ||
+ doesNodeMatchPattern(callee, 'Rome.requireNamespace')) &&
scope.getBinding('Rome') === undefined;
if (validRequireCallee || validRomeRequreCallee) {

MODIFY
packages/@romejs/js-compiler/api/analyzeDependencies/visitors/cjs.ts
packages/@romejs/js-compiler/api/analyzeDependencies/visitors/cjs.ts
@@ -60,10 +60,12 @@ export default {
if (node.type === 'AssignmentExpression') {
const isModuleExports =
path.scope.getBinding('module') === undefined &&
- doesNodeMatchPattern(node.left, 'module.exports', true);
+ (doesNodeMatchPattern(node.left, 'module.exports') ||
+ doesNodeMatchPattern(node.left, 'module.exports.**'));
const isExports =
path.scope.getBinding('exports') === undefined &&
- doesNodeMatchPattern(node.left, 'exports', true);
+ (doesNodeMatchPattern(node.left, 'exports') ||
+ doesNodeMatchPattern(node.left, 'exports.**'));
if (isModuleExports || isExports) {
context.record(new CJSExportRecord(node));

js-ast-utils: Add getNodeReferenceParts and doesNodeMatchPattern tests and improvements