코드를 다시 작성하는 코드 작성: jscodeshift

게시 됨: 2022-03-11

jscodeshift가 있는 Codemods

디렉토리 전체에서 찾기 및 바꾸기 기능을 사용하여 JavaScript 소스 파일을 변경한 적이 있습니까? 당신이 좋다면, 당신의 코드 베이스가 상당하다면 노력할 가치가 있기 때문에 당신은 멋진 것을 얻었고 캡처 그룹과 함께 정규 표현식을 사용했습니다. 그러나 정규식에는 한계가 있습니다. 사소하지 않은 변경의 경우 컨텍스트에서 코드를 이해하고 길고 지루하며 오류가 발생하기 쉬운 프로세스를 기꺼이 수행할 개발자가 필요합니다.

이것이 "codemods"가 들어오는 곳입니다.

Codemod는 다른 스크립트를 다시 작성하는 데 사용되는 스크립트입니다. 코드를 읽고 쓸 수 있는 찾기 및 바꾸기 기능으로 생각하십시오. 이를 사용하여 팀의 코딩 규칙에 맞게 소스 코드를 업데이트하거나, API가 수정될 때 광범위하게 변경하거나, 공개 패키지가 주요 변경 사항을 만들 때 기존 코드를 자동 수정하는 데 사용할 수 있습니다.

jscodeshift 툴킷은 codemods 작업에 적합합니다.

codemods를 코드를 읽고 쓸 수 있는 스크립트 찾기 및 바꾸기 기능으로 생각하십시오.
트위터

이 기사에서는 "jscodeshift"라는 코드 모드용 툴킷을 탐색하면서 복잡성이 증가하는 세 가지 코드 모드를 생성할 것입니다. 결국에는 jscodeshift의 중요한 측면에 대해 폭넓게 노출될 것이며 자신만의 codemod 작성을 시작할 준비가 될 것입니다. 우리는 몇 가지 기본적이지만 멋진 codemod 사용을 다루는 세 가지 연습을 진행할 것이며 내 github 프로젝트에서 이러한 연습의 소스 코드를 볼 수 있습니다.

jscodeshift란 무엇입니까?

jscodeshift 툴킷을 사용하면 변환을 통해 많은 소스 파일을 펌핑하고 다른 쪽 끝에서 나오는 것으로 대체할 수 있습니다. 변환 내에서 소스를 AST(추상 구문 트리)로 구문 분석하고 이리저리 살펴보고 변경한 다음 변경된 AST에서 소스를 재생성합니다.

jscodeshift가 제공하는 인터페이스는 recastast-types 패키지에 대한 래퍼입니다. recast 는 소스에서 AST로의 변환을 처리하고 ast-types 는 AST 노드와의 하위 수준 상호 작용을 처리합니다.

설정

시작하려면 npm에서 전역으로 jscodeshift를 설치하십시오.

 npm i -g jscodeshift

사용할 수 있는 러너 옵션과 Jest(오픈 소스 JavaScript 테스트 프레임워크)를 통해 테스트 모음을 실행하는 것을 매우 쉽게 만들어주는 독단적인 테스트 설정이 있지만 지금은 단순성을 위해 이를 우회할 것입니다.

jscodeshift -t some-transform.js input-file.js -d -p

이것은 변환 some-transform.js 를 통해 input-file.js 를 실행하고 파일을 변경하지 않고 결과를 인쇄합니다.

하지만 시작하기 전에 jscodeshift API가 처리하는 세 가지 주요 객체 유형인 노드, 노드 경로 및 컬렉션을 이해하는 것이 중요합니다.

노드

노드는 종종 "AST 노드"라고 하는 AST의 기본 빌딩 블록입니다. 다음은 AST Explorer로 코드를 탐색할 때 표시되는 것입니다. 그것들은 단순한 객체이며 어떠한 메소드도 제공하지 않습니다.

노드 경로

노드 경로는 추상 구문 트리를 가로지르는 방법으로 ast-types 에서 제공하는 AST 노드 주위의 래퍼입니다(AST, 기억하시나요?). 별도로 노드에는 상위 또는 범위에 대한 정보가 없으므로 노드 경로가 이를 처리합니다. node 속성을 통해 래핑된 노드에 액세스할 수 있으며 기본 노드를 변경하는 데 사용할 수 있는 몇 가지 방법이 있습니다. 노드 경로는 종종 "경로"라고 합니다.

컬렉션

컬렉션은 AST를 쿼리할 때 jscodeshift API가 반환하는 0개 이상의 노드 경로 그룹입니다. 그들은 모든 종류의 유용한 방법을 가지고 있으며 그 중 일부는 우리가 탐구할 것입니다.

컬렉션에는 노드 경로가 포함되고 노드 경로에는 노드가 포함되며 노드는 AST를 구성하는 요소입니다. 이를 염두에 두시면 jscodeshift 쿼리 API를 쉽게 이해할 수 있습니다.

이러한 객체와 해당 API 기능 간의 차이점을 추적하기 어려울 수 있으므로 객체 유형을 기록하고 기타 주요 정보를 제공하는 jscodeshift-helper라는 멋진 도구가 있습니다.

노드, 노드 경로 및 컬렉션의 차이점을 아는 것이 중요합니다.

노드, 노드 경로 및 컬렉션의 차이점을 아는 것이 중요합니다.

연습 1: 콘솔 호출 제거

발을 담그기 위해 코드 기반에서 모든 콘솔 메서드에 대한 호출을 제거하는 것부터 시작하겠습니다. 찾기 및 바꾸기와 약간의 정규식으로 이 작업을 수행할 수 있지만 여러 줄 문, 템플릿 리터럴 및 더 복잡한 호출에서는 까다로워지기 시작하므로 시작하기에 이상적인 예입니다.

먼저 remove-consoles.jsremove-consoles.input.js consoles.input.js라는 두 개의 파일을 만듭니다.

 //remove-consoles.js export default (fileInfo, api) => { };
 //remove-consoles.input.js export const sum = (a, b) => { console.log('calling sum with', arguments); return a + b; }; export const multiply = (a, b) => { console.warn('calling multiply with', arguments); return a * b; }; export const divide = (a, b) => { console.error(`calling divide with ${ arguments }`); return a / b; }; export const average = (a, b) => { console.log('calling average with ' + arguments); return divide(sum(a, b), 2); };

다음은 jscodeshift를 통해 푸시하기 위해 터미널에서 사용할 명령입니다.

jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p

모든 것이 올바르게 설정되었으면 실행하면 다음과 같이 표시되어야 합니다.

 Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... All done. Results: 0 errors 0 unmodified 1 skipped 0 ok Time elapsed: 0.514seconds

좋습니다. 우리의 변환이 실제로 아직 아무 것도 하지 않기 때문에 그것은 약간의 반감기였습니다. 그러나 적어도 우리는 그것이 모두 작동하고 있다는 것을 압니다. 전혀 실행되지 않으면 jscodeshift를 전역으로 설치했는지 확인하십시오. 변환을 실행하는 명령이 잘못된 경우 "오류 변환 파일이 존재하지 않습니다" 메시지가 표시되거나 입력 파일을 찾을 수 없는 경우 "TypeError: 경로는 문자열 또는 버퍼여야 합니다."가 표시됩니다. 당신이 뭔가를 뚱뚱하게 만들었다면 매우 설명적인 변환 오류로 쉽게 발견할 수 있을 것입니다.

관련: Toptal의 빠르고 실용적인 JavaScript 치트 시트: ES6 이상

성공적인 변환 후 최종 목표는 다음 소스를 확인하는 것입니다.

 export const sum = (a, b) => { return a + b; }; export const multiply = (a, b) => { return a * b; }; export const divide = (a, b) => { return a / b; }; export const average = (a, b) => { return divide(sum(a, b), 2); };

거기에 도달하려면 소스를 AST로 변환하고 콘솔을 찾아 제거한 다음 변경된 AST를 소스로 다시 변환해야 합니다. 첫 번째 단계와 마지막 단계는 간단합니다.

 remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };

그러나 콘솔을 찾고 제거하는 방법은 무엇입니까? Mozilla Parser API에 대한 특별한 지식이 없는 한 AST가 어떻게 생겼는지 이해하는 데 도움이 되는 도구가 필요할 것입니다. 이를 위해 AST Explorer를 사용할 수 있습니다. remove-consoles.input.js 의 내용을 붙여넣으면 AST가 표시됩니다. 가장 단순한 코드에도 데이터가 많기 때문에 위치 데이터와 메소드를 숨기는 데 도움이 됩니다. 트리 위의 확인란을 사용하여 AST Explorer에서 속성의 가시성을 토글할 수 있습니다.

콘솔 메서드에 대한 호출을 CallExpressions 라고 하는 것을 볼 수 있습니다. 그렇다면 변환에서 어떻게 찾을 수 있을까요? 컬렉션, 노드 경로 및 노드 자체 간의 차이점에 대한 이전 논의를 기억하면서 jscodeshift의 쿼리를 사용합니다.

 //remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };

const root = j(fileInfo.source); 루트 AST 노드를 래핑하는 하나의 노드 경로 컬렉션을 반환합니다. 컬렉션의 find 메소드를 사용하여 다음과 같이 특정 유형의 자손 노드를 검색할 수 있습니다.

 const callExpressions = root.find(j.CallExpression);

이것은 CallExpressions인 노드만 포함하는 노드 경로의 또 다른 컬렉션을 반환합니다. 언뜻 보면 이것이 우리가 원하는 것처럼 보이지만 너무 광범위합니다. 변환을 통해 수백 또는 수천 개의 파일을 실행하게 될 수 있으므로 의도한 대로 작동할 것이라는 확신을 가지려면 정확해야 합니다. 위의 순진한 find 는 콘솔 CallExpressions를 찾을 뿐만 아니라 다음을 포함하여 소스의 모든 CallExpression을 찾습니다.

 require('foo') bar() setTimeout(() => {}, 0)

더 큰 특수성을 부여하기 위해 .find 에 두 번째 인수를 제공합니다. 추가 매개변수의 객체로 각 노드는 결과에 포함되어야 합니다. AST Explorer를 보면 console.* 호출의 형식이 다음과 같은지 확인할 수 있습니다.

 { "type": "CallExpression", "callee": { "type": "MemberExpression", "object": { "type": "Identifier", "name": "console" } } }

이러한 지식을 바탕으로 우리는 관심 있는 CallExpressions 유형만 반환하는 지정자로 쿼리를 구체화할 수 있습니다.

 const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, });

이제 호출 사이트의 정확한 컬렉션을 얻었으므로 AST에서 제거하겠습니다. 편리하게도 컬렉션 개체 유형에는 바로 그 작업을 수행하는 remove 메서드가 있습니다. 이제 remove-consoles.js 파일이 다음과 같이 보일 것입니다.

 //remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source) const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, } ); callExpressions.remove(); return root.toSource(); };

이제 jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p 를 사용하여 명령줄에서 변환을 실행하면 다음이 표시되어야 합니다.

 Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... export const sum = (a, b) => { return a + b; }; export const multiply = (a, b) => { return a * b; }; export const divide = (a, b) => { return a / b; }; export const average = (a, b) => { return divide(sum(a, b), 2); }; All done. Results: 0 errors 0 unmodified 0 skipped 1 ok Time elapsed: 0.604seconds

좋아 보인다. 이제 변환이 기본 AST를 변경하므로 .toSource() 를 사용하여 원본과 다른 문자열을 생성합니다. 명령의 -p 옵션은 결과를 표시하고 처리된 각 파일에 대한 처리 집계가 맨 아래에 표시됩니다. 명령에서 -d 옵션을 제거하면 remove-consoles.input.js의 내용이 변환의 출력으로 바뀝니다.

첫 번째 연습은 거의 완료되었습니다. 코드는 기괴하게 생겼고 아마도 기능적인 순수주의자에게 매우 공격적일 것입니다. 따라서 변환 코드 흐름을 더 좋게 만들기 위해 jscodeshift는 대부분의 것을 연결 가능하게 만들었습니다. 이를 통해 다음과 같이 변환을 다시 작성할 수 있습니다.

 // remove-consoles.js export default (fileInfo, api) => { const j = api.jscodeshift; return j(fileInfo.source) .find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, } ) .remove() .toSource(); };

훨씬 낫다. 연습 1을 요약하기 위해 소스를 래핑하고 노드 경로 모음을 쿼리하고 AST를 변경한 다음 해당 소스를 재생성했습니다. 우리는 매우 간단한 예를 통해 우리의 발을 적시고 가장 중요한 측면을 다뤘습니다. 이제 더 흥미로운 것을 해보자.

연습 2: 가져온 메서드 호출 바꾸기

이 시나리오의 경우 "getCircleArea"를 위해 더 이상 사용되지 않는 "circleArea"라는 메서드가 있는 "지오메트리" 모듈이 있습니다. 이를 /geometry\.circleArea/g 로 쉽게 찾아서 바꿀 수 있지만 사용자가 모듈을 가져와서 다른 이름을 할당했다면 어떻게 될까요? 예를 들어:

 import g from 'geometry'; const area = g.circleArea(radius);

geometry.circleArea 대신 g.circleArea 를 대체하는 방법을 알 수 있습니까? 우리는 확실히 모든 circleArea 호출이 우리가 찾고 있는 호출이라고 가정할 수 없으며 약간의 컨텍스트가 필요합니다. 여기에서 codemod가 값을 표시하기 시작합니다. deprecated.jsdeprecated.input.js 라는 두 개의 파일을 만드는 것으로 시작하겠습니다.

 //deprecated.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
 deprecated.input.js import g from 'geometry'; import otherModule from 'otherModule'; const radius = 20; const area = g.circleArea(radius); console.log(area === Math.pow(g.getPi(), 2) * radius); console.log(area === otherModule.circleArea(radius));

이제 이 명령을 실행하여 codemod를 실행하십시오.

jscodeshift -t ./deprecated.js ./deprecated.input.js -d -p

변환이 실행되었지만 아직 변경되지 않았음을 나타내는 출력이 표시되어야 합니다.

 Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... All done. Results: 0 errors 1 unmodified 0 skipped 0 ok Time elapsed: 0.892seconds

geometry 모듈이 무엇으로 임포트되었는지 알아야 합니다. AST Explorer를 보고 우리가 찾고 있는 것이 무엇인지 알아봅시다. 우리의 수입은 이 형태를 취합니다.

 { "type": "ImportDeclaration", "specifiers": [ { "type": "ImportDefaultSpecifier", "local": { "type": "Identifier", "name": "g" } } ], "source": { "type": "Literal", "value": "geometry" } }

다음과 같이 노드 컬렉션을 찾기 위해 객체 유형을 지정할 수 있습니다.

 const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'geometry', }, });

이것은 "기하학"을 가져오는 데 사용되는 ImportDeclaration을 가져옵니다. 거기에서 가져온 모듈을 보관하는 데 사용되는 로컬 이름을 찾기 위해 아래로 파고듭니다. 이번이 처음이라 처음 시작할 때 중요하고 헷갈리는 점을 지적해 보겠습니다.

참고: root.find() 가 노드 경로 모음을 반환한다는 것을 아는 것이 중요합니다. 거기에서 .get(n) 메서드는 해당 컬렉션의 인덱스 n 에 있는 노드 경로를 반환하고 실제 노드를 가져오기 위해 .node 를 사용합니다. 노드는 기본적으로 AST Explorer에서 볼 수 있는 것입니다. node-path는 대부분 노드 자체가 아니라 노드의 범위와 관계에 대한 정보임을 기억하십시오.

 // find the Identifiers const identifierCollection = importDeclaration.find(j.Identifier); // get the first NodePath from the Collection const nodePath = identifierCollection.get(0); // get the Node in the NodePath and grab its "name" const localName = nodePath.node.name;

이를 통해 geometry 모듈이 무엇으로 임포트되었는지 동적으로 파악할 수 있습니다. 다음으로 사용 중인 장소를 찾아 변경합니다. AST Explorer를 보면 다음과 같은 MemberExpressions를 찾아야 함을 알 수 있습니다.

 { "type": "MemberExpression", "object": { "name": "geometry" }, "property": { "name": "circleArea" } }

그러나 모듈을 다른 이름으로 가져왔을 수 있으므로 쿼리를 대신 다음과 같이 표시하여 이에 대해 설명해야 합니다.

 j.MemberExpression, { object: { name: localName, }, property: { name: "circleArea", }, })

이제 쿼리가 있으므로 이전 메서드에 대한 모든 호출 사이트 모음을 가져온 다음 컬렉션의 replaceWith() 메서드를 사용하여 교체할 수 있습니다. replaceWith() 메서드는 컬렉션을 반복하여 각 노드 경로를 콜백 함수에 전달합니다. 그런 다음 AST 노드는 콜백에서 반환하는 노드로 대체됩니다.

Codemod를 사용하면 리팩토링을 위한 '지능형' 고려 사항을 스크립팅할 수 있습니다.

다시 말하지만, 이를 이해하려면 컬렉션, 노드 경로 및 노드의 차이점을 이해해야 합니다.

교체 작업이 끝나면 평소와 같이 소스를 생성합니다. 완성된 변환은 다음과 같습니다.

 //deprecated.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for "geometry" import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'geometry', }, }); // get the local name for the imported module const localName = // find the Identifiers importDeclaration.find(j.Identifier) // get the first NodePath from the Collection .get(0) // get the Node in the NodePath and grab its "name" .node.name; return root.find(j.MemberExpression, { object: { name: localName, }, property: { name: 'circleArea', }, }) .replaceWith(nodePath => { // get the underlying Node const { node } = nodePath; // change to our new prop node.property.name = 'getCircleArea'; // replaceWith should return a Node, not a NodePath return node; }) .toSource(); };

변환을 통해 소스를 실행하면 geometry 모듈에서 더 이상 사용되지 않는 메소드에 대한 호출이 변경되었지만 나머지는 다음과 같이 변경되지 않은 채로 남아 있음을 알 수 있습니다.

 import g from 'geometry'; import otherModule from 'otherModule'; const radius = 20; const area = g.getCircleArea(radius); console.log(area === Math.pow(g.getPi(), 2) * radius); console.log(area === otherModule.circleArea(radius));

연습 3: 메서드 서명 변경

이전 연습에서 특정 유형의 노드에 대한 컬렉션 쿼리, 노드 제거 및 노드 변경에 대해 다루었지만 완전히 새로운 노드를 생성하는 것은 어떻습니까? 이것이 이 연습에서 다룰 내용입니다.

이 시나리오에서는 소프트웨어가 성장함에 따라 개별 인수로 제어할 수 없는 메서드 서명이 있으므로 대신 해당 인수를 포함하는 개체를 받아들이는 것이 더 낫다고 결정했습니다.

car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true); 대신

우리는보고 싶습니다

 const suv = car.factory({ color: 'white', make: 'Kia', model: 'Sorento', year: 2010, miles: 50000, bedliner: null, alarm: true, });

테스트할 변환 및 입력 파일을 만드는 것으로 시작하겠습니다.

 //signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); return root.toSource(); };
 //signature-change.input.js import car from 'car'; const suv = car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true); const truck = car.factory('silver', 'Toyota', 'Tacoma', 2006, 100000, true, true);

변환을 실행하는 명령은 jscodeshift -t signature-change.js signature-change.input.js -d -p 이며 이 변환을 수행하는 데 필요한 단계는 다음과 같습니다.

  • 가져온 모듈의 로컬 이름 찾기
  • .factory 메소드에 대한 모든 호출 사이트 찾기
  • 전달되는 모든 인수 읽기
  • 해당 호출을 원래 값이 있는 개체를 포함하는 단일 인수로 교체합니다.

AST Explorer와 이전 연습에서 사용한 프로세스를 사용하면 처음 두 단계는 쉽습니다.

 //signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for "car" import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'car', }, }); // get the local name for the imported module const localName = importDeclaration.find(j.Identifier) .get(0) .node.name; // find where `.factory` is being called return root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { name: localName, }, property: { name: 'factory', }, } }) .toSource(); };

현재 전달되는 모든 인수를 읽기 위해 CallExpressions 컬렉션에서 replaceWith() 메서드를 사용하여 각 노드를 교환합니다. 새 노드는 node.arguments를 새로운 단일 인수인 객체로 대체합니다.

jscodeshift로 메소드 인수를 쉽게 교환하세요!

'replacewith()'로 메서드 서명을 변경하고 전체 노드를 교체합니다.

적절한 값을 사용하기 전에 이것이 어떻게 작동하는지 확인하기 위해 간단한 객체로 시도해 보겠습니다.

 .replaceWith(nodePath => { const { node } = nodePath; node.arguments = [{ foo: 'bar' }]; return node; })

이것을 실행하면( jscodeshift -t signature-change.js signature-change.input.js -d -p ) 변환은 다음과 같이 폭발합니다.

 ERR signature-change.input.js Transformation error Error: {foo: bar} does not match type Printable

일반 개체를 AST 노드에 끼울 수는 없습니다. 대신 빌더를 사용하여 적절한 노드를 만들어야 합니다.

관련: 상위 3%의 프리랜서 Javascript 개발자를 고용하십시오.

노드 빌더

빌더를 사용하면 새 노드를 적절하게 생성할 수 있습니다. 그것들은 ast-types 에 의해 제공되고 jscodeshift를 통해 표면화됩니다. 그들은 다른 유형의 노드가 올바르게 생성되었는지 엄격하게 확인합니다. 이는 롤을 해킹할 때 답답할 수 있지만 궁극적으로 이것은 좋은 것입니다. 빌더를 사용하는 방법을 이해하려면 두 가지 사항을 염두에 두어야 합니다.

사용 가능한 모든 AST 노드 유형은 ast-types github 프로젝트의 def 폴더에 정의되어 있으며 대부분 core.js에 있습니다. 모든 AST 노드 유형에 대한 빌더가 있지만 파스칼이 아닌 낙타 케이스 버전의 노드 유형을 사용합니다 -사례. (이것은 명시적으로 명시되어 있지 않지만 ast-types 소스의 경우임을 알 수 있습니다.

원하는 결과의 예와 함께 AST Explorer를 사용하면 이것을 아주 쉽게 조합할 수 있습니다. 우리의 경우 새로운 단일 인수가 많은 속성을 가진 ObjectExpression이 되기를 원합니다. 위에서 언급한 유형 정의를 보면 이것이 무엇을 수반하는지 알 수 있습니다.

 def("ObjectExpression") .bases("Expression") .build("properties") .field("properties", [def("Property")]); def("Property") .bases("Node") .build("kind", "key", "value") .field("kind", or("init", "get", "set")) .field("key", or(def("Literal"), def("Identifier"))) .field("value", def("Expression"));

따라서 { foo: 'bar' }에 대한 AST 노드를 빌드하는 코드는 다음과 같습니다.

 j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]);

해당 코드를 가져 와서 다음과 같이 변환에 연결하십시오.

 .replaceWith(nodePath => { const { node } = nodePath; const object = j.objectExpression([ j.property( 'init', j.identifier('foo'), j.literal('bar') ) ]); node.arguments = [object]; return node; })

이것을 실행하면 다음과 같은 결과를 얻을 수 있습니다.

 import car from 'car'; const suv = car.factory({ foo: "bar" }); const truck = car.factory({ foo: "bar" });

이제 적절한 AST 노드를 만드는 방법을 알았으므로 이전 인수를 반복하고 대신 사용할 새 개체를 생성하는 것이 쉽습니다. 다음은 현재 signature-change.js 파일의 모습입니다.

 //signature-change.js export default (fileInfo, api) => { const j = api.jscodeshift; const root = j(fileInfo.source); // find declaration for "car" import const importDeclaration = root.find(j.ImportDeclaration, { source: { type: 'Literal', value: 'car', }, }); // get the local name for the imported module const localName = importDeclaration.find(j.Identifier) .get(0) .node.name; // current order of arguments const argKeys = [ 'color', 'make', 'model', 'year', 'miles', 'bedliner', 'alarm', ]; // find where `.factory` is being called return root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { name: localName, }, property: { name: 'factory', }, } }) .replaceWith(nodePath => { const { node } = nodePath; // use a builder to create the ObjectExpression const argumentsAsObject = j.objectExpression( // map the arguments to an Array of Property Nodes node.arguments.map((arg, i) => j.property( 'init', j.identifier(argKeys[i]), j.literal(arg.value) ) ) ); // replace the arguments with our new ObjectExpression node.arguments = [argumentsAsObject]; return node; }) // specify print options for recast .toSource({ quote: 'single', trailingComma: true }); };

변환( jscodeshift -t signature-change.js signature-change.input.js -d -p )을 실행하면 서명이 예상대로 업데이트된 것을 볼 수 있습니다.

 import car from 'car'; const suv = car.factory({ color: 'white', make: 'Kia', model: 'Sorento', year: 2010, miles: 50000, bedliner: null, alarm: true, }); const truck = car.factory({ color: 'silver', make: 'Toyota', model: 'Tacoma', year: 2006, miles: 100000, bedliner: true, alarm: true, });

jscodeshift 요약이 포함된 Codemods

이 지점에 도달하는 데 약간의 시간과 노력이 필요했지만 대량 리팩토링에 직면했을 때 이점은 엄청납니다. 파일 그룹을 다른 프로세스에 배포하고 병렬로 실행하는 것은 jscodeshift의 탁월한 기능으로, 몇 초 만에 거대한 코드베이스에서 복잡한 변환을 실행할 수 있습니다. codemods에 더 능숙해지면 기존 스크립트의 용도를 변경하기 시작합니다(예: react-codemod github 저장소 또는 모든 종류의 작업에 대해 직접 작성하면 귀하, 귀하의 팀 및 패키지 사용자가 더 효율적입니다. .