<commit_before>import * as fs from 'fs';
import * as path from 'path';
import { pathToFileURL } from 'url';

import { camelCase } from 'lodash';
import {
  Range,
  TextDocumentEdit,
  TextEdit,
  WorkspaceEdit,
} from 'vscode-languageserver';

import { findSourceWord } from '../../../common/documens/find-source-word';
import { getRelatedFilePath } from '../../../common/documens/related';
import { ExtensionSettings } from '../../../feature/configuration/DocumentSettings';
import {
  findAllBindableRegions,
  findRegionsWithValue,
} from '../../regions/findSpecificRegion';
import { getClass, getClassMember } from '../../tsMorph/tsMorphClass';
import { AureliaProgram } from '../../viewModel/AureliaProgram';
import {
  RepeatForRegionData,
  ViewRegionInfo,
  ViewRegionType,
} from '../embeddedSupport';
import { LanguageMode, Position, TextDocument } from '../languageModes';

export function getBindableAttributeMode(
  aureliaProgram: AureliaProgram,
  extensionSettings: ExtensionSettings
): LanguageMode {
  return {
    getId() {
      return ViewRegionType.BindableAttribute;
    },

    async doRename(
      document: TextDocument,
      position: Position,
      newName: string,
      region: ViewRegionInfo
    ) {
      if (!region.startCol) return;
      if (!region.endCol) return;

      const { line } = position;
      const startPosition = Position.create(line - 1, region.startCol - 1);
      const endPosition = Position.create(line - 1, region.endCol - 1);
      const range = Range.create(startPosition, endPosition);

      // 1. rename view model
      const offset = document.offsetAt(position);
      const sourceWord = findSourceWord(region, offset);
      const viewModelPath = getViewModelPathFromTagName(region.tagName ?? '')!;
      performViewModelChanges(viewModelPath, sourceWord, camelCase(newName));

      // 2. rename all others
      const otherComponentChanges = await getAllOtherChangesForComponentsWithBindable(
        sourceWord,
        newName
      );

      // 3. rename all regions in view of target custom element
      // 3.1 Get document of corresponding view
      const componentList = aureliaProgram.aureliaComponents.get();
      const targetComponent = componentList.find(
        (component) => component.viewModelFilePath === viewModelPath
      );
      if (!targetComponent) return;

      const viewFilePath = targetComponent.viewFilePath ?? '';
      const uri = pathToFileURL(viewFilePath).toString();
      const content = fs.readFileSync(viewFilePath, 'utf-8');
      const viewDocument = TextDocument.create(uri, 'html', 0, content);
      if (!viewDocument) return;

      const otherChangesInsideSameView = await renameAllOtherRegionsInSameView(
        viewDocument,
        sourceWord,
        newName
      );

      return {
        changes: {
          [document.uri]: [TextEdit.replace(range, newName)],
          ...otherComponentChanges,
          ...otherChangesInsideSameView,
        },

        // documentChanges: [
        //   TextDocumentEdit.create(
        //     { version: document.version + 1, uri: document.uri },
        //     [TextEdit.replace(range, newName)]
        //   ),
        // ],
      };
    },

    onDocumentRemoved(_document: TextDocument) {},
    dispose() {},
  };

  async function getAllOtherChangesForComponentsWithBindable(
    bindableName: string,
    newName: string
  ) {
    const bindableRegions = await findAllBindableRegions(
      aureliaProgram,
      bindableName
    );

    const result: WorkspaceEdit['changes'] = {};
    Object.entries(bindableRegions).forEach(([uri, regions]) => {
      regions.forEach((region) => {
        const range = getRangeFromRegion(region);
        if (!range) return;

        if (result[uri] === undefined) {
          result[uri] = [];
        }
        result[uri].push(TextEdit.replace(range, newName));
      });
    });

    return result;
  }

  function performViewModelChanges(
    viewModelPath: string,
    sourceWord: string,
    newName: string
  ) {
    const components = aureliaProgram.aureliaComponents.get();
    const targetComponent = components.find(
      (component) => component.viewModelFilePath === viewModelPath
    );

    const tsMorphProject = aureliaProgram.getTsMorphProject();
    const sourceFile = tsMorphProject.getSourceFile(viewModelPath);
    const className = targetComponent?.className ?? '';
    const classNode = getClass(sourceFile, className);
    const classMemberNode = getClassMember(classNode, sourceWord);
    classMemberNode?.rename(newName);
    tsMorphProject.saveSync();
  }

  function getViewModelPathFromTagName(tagName: string): string | undefined {
    const aureliaSourceFiles = aureliaProgram.getAureliaSourceFiles();
    const targetAureliaFile = aureliaSourceFiles?.find((sourceFile) => {
      return path.parse(sourceFile.fileName).name === tagName;
    });

    /**
     * 1. Triggered on <|my-component>
     * */
    if (typeof targetAureliaFile?.fileName === 'string') {
      return targetAureliaFile.fileName;
    }
  }

  async function renameAllOtherRegionsInSameView(
    document: TextDocument,
    sourceWord: string,
    newName: string
  ) {
    const result: WorkspaceEdit['changes'] = {};

    const regions = await findRegionsWithValue(
      aureliaProgram,
      document,
      sourceWord
    );

    const { uri } = document;
    result[uri] = [];
    regions.forEach((region) => {
      const range = getRangeFromRegion(region, document);
      if (!range) return;

      result[uri].push(TextEdit.replace(range, newName));
    });

    result; /* ? */
    return result;
  }
}

function getRangeFromRegion(
  region: ViewRegionInfo,
  document?: TextDocument
): Range | undefined {
  let range;
  if (document) {
    range = getRangeFromRegionViaDocument(region, document);
  } else {
    range = getRangeFromStandardRegion(region, range);
  }

  return range;
}

function getRangeFromStandardRegion(region: ViewRegionInfo, range: any) {
  if (!region.startCol) return;
  if (!region.startLine) return;
  if (!region.endCol) return;
  if (!region.endLine) return;
<commit_msg>refac(server.rename): use rename locations instead of ts morph rename<commit_after>import * as fs from 'fs';
import * as path from 'path';
import { pathToFileURL } from 'url';

import { camelCase } from 'lodash';
import { Range, TextEdit, WorkspaceEdit } from 'vscode-languageserver';

import { findSourceWord } from '../../../common/documens/find-source-word';
import { ExtensionSettings } from '../../../feature/configuration/DocumentSettings';
import {
  findAllBindableRegions,
  findRegionsWithValue,
} from '../../regions/findSpecificRegion';
import { getClass, getClassMember } from '../../tsMorph/tsMorphClass';
import { AureliaProgram } from '../../viewModel/AureliaProgram';
import {
  RepeatForRegionData,
  ViewRegionInfo,
  ViewRegionType,
} from '../embeddedSupport';
import { LanguageMode, Position, TextDocument } from '../languageModes';
import { Logger } from '../../../common/logging/logger';

const logger = new Logger('getBindableAttributeMode');

export function getBindableAttributeMode(
  aureliaProgram: AureliaProgram,
  extensionSettings: ExtensionSettings
): LanguageMode {
  return {
    getId() {
      return ViewRegionType.BindableAttribute;
    },

    async doRename(
      document: TextDocument,
      position: Position,
      newName: string,
      region: ViewRegionInfo
    ) {
      if (!region.startCol) return;
      if (!region.endCol) return;

      const { line } = position;
      const startPosition = Position.create(line - 1, region.startCol - 1);
      const endPosition = Position.create(line - 1, region.endCol - 1);
      const range = Range.create(startPosition, endPosition);

      // 1. rename view model
      const offset = document.offsetAt(position);
      const sourceWord = findSourceWord(region, offset);
      const viewModelPath = getViewModelPathFromTagName(region.tagName ?? '')!;
      const viewModelChanes = performViewModelChanges(
        viewModelPath,
        sourceWord,
        camelCase(newName)
      );

      // 2. rename all others
      const otherComponentChanges = await getAllOtherChangesForComponentsWithBindable(
        sourceWord,
        newName
      );

      // 3. rename all regions in view of target custom element
      // 3.1 Get document of corresponding view
      const componentList = aureliaProgram.aureliaComponents.get();
      const targetComponent = componentList.find(
        (component) => component.viewModelFilePath === viewModelPath
      );
      if (!targetComponent) return;

      const viewFilePath = targetComponent.viewFilePath ?? '';
      const uri = pathToFileURL(viewFilePath).toString();
      const content = fs.readFileSync(viewFilePath, 'utf-8');
      const viewDocument = TextDocument.create(uri, 'html', 0, content);
      if (!viewDocument) return;

      const otherChangesInsideSameView = await renameAllOtherRegionsInSameView(
        viewDocument,
        sourceWord,
        newName
      );

      return {
        changes: {
          [document.uri]: [TextEdit.replace(range, newName)],
          ...viewModelChanes,
          ...otherComponentChanges,
          ...otherChangesInsideSameView,
        },

        // documentChanges: [
        //   TextDocumentEdit.create(
        //     { version: document.version + 1, uri: document.uri },
        //     [TextEdit.replace(range, newName)]
        //   ),
        // ],
      };
    },

    onDocumentRemoved(_document: TextDocument) {},
    dispose() {},
  };

  async function getAllOtherChangesForComponentsWithBindable(
    bindableName: string,
    newName: string
  ) {
    const bindableRegions = await findAllBindableRegions(
      aureliaProgram,
      bindableName
    );

    const result: WorkspaceEdit['changes'] = {};
    Object.entries(bindableRegions).forEach(([uri, regions]) => {
      regions.forEach((region) => {
        const range = getRangeFromRegion(region);
        if (!range) return;

        if (result[uri] === undefined) {
          result[uri] = [];
        }
        result[uri].push(TextEdit.replace(range, newName));
      });
    });

    return result;
  }

  function performViewModelChanges(
    viewModelPath: string,
    sourceWord: string,
    newName: string
  ): WorkspaceEdit['changes'] {
    // 1. Prepare
    const result: WorkspaceEdit['changes'] = {};
    const components = aureliaProgram.aureliaComponents.get();
    const targetComponent = components.find(
      (component) => component.viewModelFilePath === viewModelPath
    );
    const tsMorphProject = aureliaProgram.getTsMorphProject();
    const sourceFile = tsMorphProject.getSourceFile(viewModelPath);
    const uri = pathToFileURL(viewModelPath).toString();
    result[uri] = [];
    const content = fs.readFileSync(viewModelPath, 'utf-8');
    const viewModelDocument = TextDocument.create(uri, 'html', 0, content);
    const className = targetComponent?.className ?? '';
    const classNode = getClass(sourceFile, className);
    const classMemberNode = getClassMember(classNode, sourceWord);

    // 2. Find rename locations
    if (classMemberNode) {
      const renameLocations = tsMorphProject
        .getLanguageService()
        .findRenameLocations(classMemberNode);

      renameLocations.forEach((location) => {
        const textSpan = location.getTextSpan();
        const startPosition = viewModelDocument.positionAt(textSpan.getStart());
        const endPosition = viewModelDocument.positionAt(textSpan.getEnd());
        const range = Range.create(startPosition, endPosition);

        result[uri].push(TextEdit.replace(range, newName));
      });
    } else {
      logger.log('Error: No class member found');
    }

    return result;
  }

  function getViewModelPathFromTagName(tagName: string): string | undefined {
    const aureliaSourceFiles = aureliaProgram.getAureliaSourceFiles();
    const targetAureliaFile = aureliaSourceFiles?.find((sourceFile) => {
      return path.parse(sourceFile.fileName).name === tagName;
    });

    /**
     * 1. Triggered on <|my-component>
     * */
    if (typeof targetAureliaFile?.fileName === 'string') {
      return targetAureliaFile.fileName;
    }
  }

  async function renameAllOtherRegionsInSameView(
    document: TextDocument,
    sourceWord: string,
    newName: string
  ) {
    const result: WorkspaceEdit['changes'] = {};

    const regions = await findRegionsWithValue(
      aureliaProgram,
      document,
      sourceWord
    );

    const { uri } = document;
    result[uri] = [];
    regions.forEach((region) => {
      const range = getRangeFromRegion(region, document);
      if (!range) return;

      result[uri].push(TextEdit.replace(range, newName));
    });

    return result;
  }
}

function getRangeFromRegion(
  region: ViewRegionInfo,
  document?: TextDocument
): Range | undefined {
  let range;
  if (document) {
    range = getRangeFromRegionViaDocument(region, document);
  } else {
    range = getRangeFromStandardRegion(region, range);
  }

  return range;
}

function getRangeFromStandardRegion(region: ViewRegionInfo, range: any) {
  if (!region.startCol) return;
  if (!region.startLine) return;
  if (!region.endCol) return;
  if (!region.endLine) return;

{'fraction_non_alphanumeric': 0.11708527598135057, 'fraction_numerical': 0.002255978342607911, 'mean_word_length': 3.116063138347261, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '258515', 'n_tokens_mistral': 3788, 'n_tokens_neox': 3687, 'n_words': 821}