You are an AI programming assistant.
- Follow the user's requirements carefully & to the letter.
- First think step-by-step -- describe your plan for what to build in psuedocode, written out in great detail
- Then output the code in a single codeblock
- Minimize any other prose

I am writing a conformance test (similar to ESLint) in TypeScript for detecting when a user does not use `rel="noopener"` when using `window.open`. The issue this causes is breaking the bfcache integrity in the browser. Some more details here:

```
# Avoid window.opener references

In some browsers (including Chromium-based browsers) if a page was opened using window.open() or (in Chromium-based browsers prior to version 88) from a link with target=\_blank—without specifying rel="noopener"—then the opening page will have a reference to the window object of the opened page.

In addition to being a security risk, a page with a non-null window.opener reference cannot safely be put into the bfcache because that could break any pages attempting to access it.

As a result, it's best to avoid creating window.opener references by using rel="noopener" whenever possible. If your site requires opening a window and controlling it through window.postMessage() or directly referencing the window object, neither the opened window nor the opener will be eligible for bfcache.
```

Using the below example rule that looks at preventing a user from using the browser `unload` event, code up the rule for creating an error when a user uses `window.open` without `rel="noopener"`:

---
import { stripIndents } from 'common-tags';
import type { AST } from '@typescript-eslint/typescript-estree';
import {
 AST\_NODE\_TYPES,
 simpleTraverse,
} from '@typescript-eslint/typescript-estree';
import type { ConformanceError } from '../conformance-error';
import type { ProjectData } from '../project-data';
import { convertPositionToRange } from '../utils/convert-position-to-range';

const TEST\_NAME = 'BFCACHE\_INTEGRITY\_NO\_UNLOAD\_LISTENERS';

const ERROR\_MESSAGE = stripIndents`
 The \`unload\` event does not fire reliably and listening for it can prevent browser optimizations like the Back-Forward Cache (bfcache).

 Consider using the \`pagehide\`, \`visibilitychange\`, or \`beforeunload\` events instead.

 See https://web.dev/bfcache for more information.
`;

export function noUnloadListeners(
 projectData: ProjectData,
 filePath: string,
 ast: AST<{
 filePath: string;
 loc: true;
 range: false;
 }>,
): ConformanceError[] {
 const conformanceConfig = projectData.getConformanceConfig();
 if (!conformanceConfig.isTestEnabled(TEST\_NAME)) return [];

 const workspaceName = projectData.getWorkspaceName();
 const errors: ConformanceError[] = [];

 simpleTraverse(ast, {
 // Detect `window.onunload =` assignments.
 AssignmentExpression(node, \_parent) {
 if (node.type !== AST\_NODE\_TYPES.AssignmentExpression) return;

 const { left } = node;
 if (left.type !== AST\_NODE\_TYPES.MemberExpression) return;

 const { object, property } = left;
 if (object.type !== AST\_NODE\_TYPES.Identifier) return;
 if (object.name !== 'window') return;
 if (property.type !== AST\_NODE\_TYPES.Identifier) return;
 if (property.name !== 'onunload') return;

 errors.push({
 testName: TEST\_NAME,
 location: {
 workspace: workspaceName,
 filePath,
 range: convertPositionToRange(node.loc),
 },
 message: ERROR\_MESSAGE,
 });
 },
 // Detect `window.addEventListener('unload')` calls.
 CallExpression(node, \_parent) {
 if (node.type !== AST\_NODE\_TYPES.CallExpression) return;

 const { callee } = node;
 if (callee.type !== AST\_NODE\_TYPES.MemberExpression) return;

 const { object, property } = callee;
 if (object.type !== AST\_NODE\_TYPES.Identifier) return;
 if (object.name !== 'window') return;
 if (property.type !== AST\_NODE\_TYPES.Identifier) return;
 if (property.name !== 'addEventListener') return;

 const [eventTypeNode] = node.arguments;
 if (eventTypeNode?.type !== AST\_NODE\_TYPES.Literal) return;
 if (eventTypeNode.value !== 'unload') return;

 errors.push({
 testName: TEST\_NAME,
 location: {
 workspace: workspaceName,
 filePath,
 range: convertPositionToRange(node.loc),
 },
 message: ERROR\_MESSAGE,
 });
 },
 });

 return errors;
}
---
user
plan:
1. define the constants for the test name and error message.
2. create a function called `noopenwithoutnoopener` that takes in `projectdata`, `filepath`, `ast`, and `conformanceconfig`.
3. check if the conformance test is enabled for the given test name.
4. define an empty array called `errors` to hold any errors found.
5. create a `simpletraverse` call to traverse through the ast.
6. detect `window.open` calls without `rel="noopener"`. if found, push an error object to the `errors` array.
7. return the `errors` array.

code:
```typescript
---
import { stripindents } from 'common-tags';
import type { ast } from '@typescript-eslint/typescript-estree';
import {
  ast_node_types,
  simpletraverse,
} from '@typescript-eslint/typescript-estree';
import type { conformanceerror } from '../conformance-error';
import type { projectdata } from '../project-data';
import { convertpositiontorange } from '../utils/convert-position-to-range';

const test_name = 'bfcache_integrity_no_open_without_noopener';

const error_message = stripindents`
  using 'window.open()' without 'rel="noopener"' breaks the bfcache integrity in the browser. use 'window.open()' with 'rel="noopener"' to avoid this issue.
`;

export function noopenwithoutnoopener(
  projectdata: projectdata,
  filepath: string,
  ast: ast<{
    filepath: string;
    loc: true;
    range: false;
  }>,
  conformanceconfig: any,
): conformanceerror[] {
  const workspacename = projectdata.getworkspacename();
  const errors: conformanceerror[] = [];

  simpletraverse(ast, {
    // detect `window.open()` calls without 'rel="noopener"'.
    callexpression(node, _parent) {
      if (node.type !== ast_node_types.callexpression) return;

      const { callee } = node;
      if (callee.type !== ast_node_types.memberexpression) return;

      const { object, property } = callee;
      if (object.type !== ast_node_types.identifier || property.type !== ast_node_types.identifier) return;

      if (object.name !== 'window' || property.name !== 'open') return;

      if (node.arguments.some((arg) => arg.type === ast_node_types.literal && arg.value === 'nofollow' && arg.range)) return;

      errors.push({
        testname: test_name,
        location: {
          workspace: workspacename,
          filepath,
          range: convertpositiontorange(node.loc),
        },
        message: error_message,
      });
    },
  });

  return errors;
}

```
assistant
Plan:
1. Define the constants for the test name and error message.
2. Create a function called `noOpenWithoutNoopener` that takes in `projectData`, `filePath`, `ast`, and `conformanceConfig`.
3. Check if the conformance test is enabled for the given test name.
4. Define an empty array called `errors` to hold any errors found.
5. Create a `simpleTraverse` call to traverse through the AST.
6. Detect `window.open` calls without `rel="noopener"`. If found, push an error object to the `errors` array.
7. Return the `errors` array.

Code:
```typescript
---
import { stripIndents } from 'common-tags';
import type { AST } from '@typescript-eslint/typescript-estree';
import {
  AST_NODE_TYPES,
  simpleTraverse,
} from '@typescript-eslint/typescript-estree';
import type { ConformanceError } from '../conformance-error';
import type { ProjectData } from '../project-data';
import { convertPositionToRange } from '../utils/convert-position-to-range';

const TEST_NAME = 'BFCACHE_INTEGRITY_NO_OPEN_WITHOUT_NOOPENER';

const ERROR_MESSAGE = stripIndents`
  Using 'window.open()' without 'rel="noopener"' breaks the bfcache integrity in the browser. Use 'window.open()' with 'rel="noopener"' to avoid this issue.
`;

export function noOpenWithoutNoopener(
  projectData: ProjectData,
  filePath: string,
  ast: AST<{
    filePath: string;
    loc: true;
    range: false;
  }>,
  conformanceConfig: any,
): ConformanceError[] {
  const workspaceName = projectData.getWorkspaceName();
  const errors: ConformanceError[] = [];

  simpleTraverse(ast, {
    // Detect `window.open()` calls without 'rel="noopener"'.
    CallExpression(node, _parent) {
      if (node.type !== AST_NODE_TYPES.CallExpression) return;

      const { callee } = node;
      if (callee.type !== AST_NODE_TYPES.MemberExpression) return;

      const { object, property } = callee;
      if (object.type !== AST_NODE_TYPES.Identifier || property.type !== AST_NODE_TYPES.Identifier) return;

      if (object.name !== 'window' || property.name !== 'open') return;

      if (node.arguments.some((arg) => arg.type === AST_NODE_TYPES.Literal && arg.value === 'nofollow' && arg.range)) return;

      errors.push({
        testName: TEST_NAME,
        location: {
          workspace: workspaceName,
          filePath,
          range: convertPositionToRange(node.loc),
        },
        message: ERROR_MESSAGE,
      });
    },
  });

  return errors;
}

```
assistant