Skip to content
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

feat: add getAccessKind #398

Merged
merged 6 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/nodes/access.test.ts
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some more tests 🙂

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some, ran out of time+energy to add more. Can file a followup issue.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ts from "typescript";
import { describe, expect, it } from "vitest";

import { createNodeAndSourceFile } from "../test/utils";
import { AccessKind, getAccessKind } from "./access";

describe("getAccessKind", () => {
it("returns AccessKind.None when the node is not an access", () => {
const { node } = createNodeAndSourceFile<ts.Expression>("let value;");

const actual = getAccessKind(node);

expect(actual).toBe(AccessKind.None);
});

it("returns AccessKind.Read when the node is a read", () => {
const { node } = createNodeAndSourceFile<ts.Expression>("abc.def;");

const actual = getAccessKind(node);

expect(actual).toBe(AccessKind.Read);
});

it("returns AccessKind.Write when the node is a write", () => {
const { node } = createNodeAndSourceFile<ts.BinaryExpression>("abc = ghi;");

const actual = getAccessKind(node.left);

expect(actual).toBe(AccessKind.Write);
});

it("returns AccessKind.Delete when the node is a delete", () => {
const { node } =
createNodeAndSourceFile<ts.DeleteExpression>("delete abc.def;");

const actual = getAccessKind(node.expression);

expect(actual).toBe(AccessKind.Delete);
});

it("returns AccessKind.ReadWrite when the node reads and writes", () => {
const { node } =
createNodeAndSourceFile<ts.BinaryExpression>("abc.def += 1;");

const actual = getAccessKind(node.left);

expect(actual).toBe(AccessKind.ReadWrite);
});
});
212 changes: 212 additions & 0 deletions src/nodes/access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Code largely based on https://github.com/ajafff/tsutils
// Original license: https://github.com/ajafff/tsutils/blob/26b195358ec36d59f00333115aa3ffd9611ca78b/LICENSE

import ts from "typescript";

import { isAssignmentKind } from "../syntax";

/* eslint-disable perfectionist/sort-enums */
/**
* What operations(s), if any, an expression applies.
*/
export enum AccessKind {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be better to express these in bitwise notation, to make it clear that's how they are supposed to be used?

export enum AccessKind {
  None = 0,
	Read = 1 << 0,
	Write = 1 << 1,
	Delete = 1 << 2,
	ReadWrite = Read | Write,
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a bit unnecessary though.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

None = 0,
Read = 1,
Write = 2,
Delete = 4,
ReadWrite = Read | Write,
/* eslint-enable perfectionist/sort-enums */
}

/**
* Determines which operation(s), if any, an expression applies.
* @example
* ```ts
* declare const node: ts.Expression;
*
* if (getAccessKind(node).Write & AccessKind.Write) !== 0) {
* // this is a reassignment (write)
* }
* ```
*/
export function getAccessKind(node: ts.Expression): AccessKind {
const parent = node.parent;
switch (parent.kind) {
case ts.SyntaxKind.DeleteExpression:
return AccessKind.Delete;
case ts.SyntaxKind.PostfixUnaryExpression:
return AccessKind.ReadWrite;

Check warning on line 38 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L38

Added line #L38 was not covered by tests
case ts.SyntaxKind.PrefixUnaryExpression:
return (parent as ts.PrefixUnaryExpression).operator ===
ts.SyntaxKind.PlusPlusToken ||
(parent as ts.PrefixUnaryExpression).operator ===
ts.SyntaxKind.MinusMinusToken
? AccessKind.ReadWrite
: AccessKind.Read;

Check warning on line 45 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L40-L45

Added lines #L40 - L45 were not covered by tests
case ts.SyntaxKind.BinaryExpression:
return (parent as ts.BinaryExpression).right === node
? AccessKind.Read

Check warning on line 48 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L48

Added line #L48 was not covered by tests
: !isAssignmentKind((parent as ts.BinaryExpression).operatorToken.kind)
? AccessKind.Read

Check warning on line 50 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L50

Added line #L50 was not covered by tests
: (parent as ts.BinaryExpression).operatorToken.kind ===
ts.SyntaxKind.EqualsToken
? AccessKind.Write
: AccessKind.ReadWrite;
case ts.SyntaxKind.ShorthandPropertyAssignment:
return (parent as ts.ShorthandPropertyAssignment)
.objectAssignmentInitializer === node
? AccessKind.Read
: isInDestructuringAssignment(parent as ts.ShorthandPropertyAssignment)
? AccessKind.Write
: AccessKind.Read;

Check warning on line 61 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L56-L61

Added lines #L56 - L61 were not covered by tests
case ts.SyntaxKind.PropertyAssignment:
return (parent as ts.PropertyAssignment).name === node
? AccessKind.None
: isInDestructuringAssignment(parent as ts.PropertyAssignment)
? AccessKind.Write
: AccessKind.Read;

Check warning on line 67 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L63-L67

Added lines #L63 - L67 were not covered by tests
case ts.SyntaxKind.ArrayLiteralExpression:
case ts.SyntaxKind.SpreadElement:
case ts.SyntaxKind.SpreadAssignment:
return isInDestructuringAssignment(
parent as
| ts.ArrayLiteralExpression
| ts.SpreadAssignment
| ts.SpreadElement,
)
? AccessKind.Write
: AccessKind.Read;

Check warning on line 78 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L71-L78

Added lines #L71 - L78 were not covered by tests
case ts.SyntaxKind.ParenthesizedExpression:
case ts.SyntaxKind.NonNullExpression:
case ts.SyntaxKind.TypeAssertionExpression:
case ts.SyntaxKind.AsExpression:
// (<number>foo! as {})++
return getAccessKind(parent as ts.Expression);

Check warning on line 84 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L83-L84

Added lines #L83 - L84 were not covered by tests
case ts.SyntaxKind.ForOfStatement:
case ts.SyntaxKind.ForInStatement:
return (parent as ts.ForInOrOfStatement).initializer === node
? AccessKind.Write
: AccessKind.Read;

Check warning on line 89 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L87-L89

Added lines #L87 - L89 were not covered by tests
case ts.SyntaxKind.ExpressionWithTypeArguments:
return (
(parent as ts.ExpressionWithTypeArguments).parent as ts.HeritageClause
).token === ts.SyntaxKind.ExtendsKeyword &&
parent.parent.parent.kind !== ts.SyntaxKind.InterfaceDeclaration
? AccessKind.Read
: AccessKind.None;

Check warning on line 96 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L91-L96

Added lines #L91 - L96 were not covered by tests
case ts.SyntaxKind.ComputedPropertyName:
case ts.SyntaxKind.ExpressionStatement:
case ts.SyntaxKind.TypeOfExpression:
case ts.SyntaxKind.ElementAccessExpression:
case ts.SyntaxKind.ForStatement:
case ts.SyntaxKind.IfStatement:
case ts.SyntaxKind.DoStatement:
case ts.SyntaxKind.WhileStatement:
case ts.SyntaxKind.SwitchStatement:
case ts.SyntaxKind.WithStatement:
case ts.SyntaxKind.ThrowStatement:
case ts.SyntaxKind.CallExpression:
case ts.SyntaxKind.NewExpression:
case ts.SyntaxKind.TaggedTemplateExpression:
case ts.SyntaxKind.JsxExpression:
case ts.SyntaxKind.Decorator:
case ts.SyntaxKind.TemplateSpan:
case ts.SyntaxKind.JsxOpeningElement:
case ts.SyntaxKind.JsxSelfClosingElement:
case ts.SyntaxKind.JsxSpreadAttribute:
case ts.SyntaxKind.VoidExpression:
case ts.SyntaxKind.ReturnStatement:
case ts.SyntaxKind.AwaitExpression:
case ts.SyntaxKind.YieldExpression:
case ts.SyntaxKind.ConditionalExpression:
case ts.SyntaxKind.CaseClause:
case ts.SyntaxKind.JsxElement:
return AccessKind.Read;
case ts.SyntaxKind.ArrowFunction:
return (parent as ts.ArrowFunction).body === node
? AccessKind.Read
: AccessKind.Write;

Check warning on line 128 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L126-L128

Added lines #L126 - L128 were not covered by tests
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.VariableDeclaration:
case ts.SyntaxKind.Parameter:
case ts.SyntaxKind.EnumMember:
case ts.SyntaxKind.BindingElement:
case ts.SyntaxKind.JsxAttribute:
return (parent as ts.JsxAttribute).initializer === node
? AccessKind.Read
: AccessKind.None;

Check warning on line 137 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L135-L137

Added lines #L135 - L137 were not covered by tests
case ts.SyntaxKind.PropertyAccessExpression:
return (parent as ts.PropertyAccessExpression).expression === node
? AccessKind.Read
: AccessKind.None;

Check warning on line 141 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L139-L141

Added lines #L139 - L141 were not covered by tests
case ts.SyntaxKind.ExportAssignment:
return (parent as ts.ExportAssignment).isExportEquals
? AccessKind.Read
: AccessKind.None;

Check warning on line 145 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L143-L145

Added lines #L143 - L145 were not covered by tests
}

return AccessKind.None;
}

function isInDestructuringAssignment(
node:
| ts.ArrayLiteralExpression
| ts.ObjectLiteralExpression
| ts.PropertyAssignment
| ts.ShorthandPropertyAssignment
| ts.SpreadAssignment
| ts.SpreadElement,
): boolean {
switch (node.kind) {
case ts.SyntaxKind.ShorthandPropertyAssignment:
if (node.objectAssignmentInitializer !== undefined) {
return true;
}

// falls through
case ts.SyntaxKind.PropertyAssignment:
case ts.SyntaxKind.SpreadAssignment:
node = node.parent as
| ts.ArrayLiteralExpression
| ts.ObjectLiteralExpression;
break;
case ts.SyntaxKind.SpreadElement:
if (node.parent.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
return false;
}

node = node.parent;
}

while (true) {
switch (node.parent.kind) {
case ts.SyntaxKind.BinaryExpression:
return (
(node.parent as ts.BinaryExpression).left === node &&
(node.parent as ts.BinaryExpression).operatorToken.kind ===
ts.SyntaxKind.EqualsToken
);
case ts.SyntaxKind.ForOfStatement:
return (node.parent as ts.ForOfStatement).initializer === node;
case ts.SyntaxKind.ArrayLiteralExpression:
case ts.SyntaxKind.ObjectLiteralExpression:
node = node.parent as
| ts.ArrayLiteralExpression
| ts.ObjectLiteralExpression;
break;
case ts.SyntaxKind.SpreadAssignment:
case ts.SyntaxKind.PropertyAssignment:
node = node.parent.parent as ts.ObjectLiteralExpression;
break;
case ts.SyntaxKind.SpreadElement:
if (node.parent.parent.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
return false;
}

node = node.parent.parent as ts.ArrayLiteralExpression;
break;
default:
return false;
}
}
}

Check warning on line 212 in src/nodes/access.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/access.ts#L151-L212

Added lines #L151 - L212 were not covered by tests
1 change: 1 addition & 0 deletions src/nodes/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./access";

Check warning on line 1 in src/nodes/index.ts

View check run for this annotation

Codecov / codecov/patch

src/nodes/index.ts#L1

Added line #L1 was not covered by tests
export * from "./typeGuards";
Loading