Skip to content

Commit e582234

Browse files
author
Shailesh Pachbhai
authored
Merge pull request #244 from salesforcecli/u/shailesh-sf/W-16375935/automate-lwc-parser-updated
@w-16375935@: LWC parser automated changes, fix build errors
2 parents 88bafd6 + 9cd9fee commit e582234

File tree

15 files changed

+346
-79
lines changed

15 files changed

+346
-79
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"bugs": "https://github.com/forcedotcom/cli/issues",
77
"dependencies": {
88
"@apexdevtools/apex-parser": "^4.1.0",
9-
"@babel/parser": "^7.25.4",
9+
"@babel/types": "^7.25.6",
1010
"@oclif/command": "^1",
1111
"@oclif/config": "^1",
1212
"@oclif/errors": "^1",
@@ -24,7 +24,7 @@
2424
"xmldom": "^0.6.0"
2525
},
2626
"devDependencies": {
27-
"@babel/parser": "^7.25.4",
27+
"@babel/parser": "^7.25.6",
2828
"@oclif/dev-cli": "^1",
2929
"@oclif/plugin-command-snapshot": "^3.3.15",
3030
"@oclif/plugin-help": "^3",
@@ -36,6 +36,7 @@
3636
"@salesforce/ts-sinon": "^1",
3737
"@types/babel__traverse": "^7.20.6",
3838
"@types/jsforce": "^1.11.5",
39+
"@types/mocha": "^10.0.8",
3940
"@typescript-eslint/eslint-plugin": "^4.2.0",
4041
"@typescript-eslint/parser": "^4.2.0",
4142
"chai": "^4.4.1",

src/commands/omnistudio/migration/OmnistudioRelatedObjectMigrationFacade.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RelatedObjectsMigrate } from '../../../migration/interfaces';
88
import { sfProject } from '../../../utils/sfcli/project/sfProject';
99
import { Logger } from '../../../utils/logger';
1010
import { ApexMigration } from '../../../migration/related/ApexMigration';
11+
import { LwcMigration } from '../../../migration/related/LwcMigration';
1112

1213
// Initialize Messages with the current plugin directory
1314
// Messages.importMessagesDirectory(__dirname);
@@ -44,7 +45,7 @@ export default class OmnistudioRelatedObjectMigrationFacade {
4445
debugTimer.start();
4546
// Initialize migration tools based on the relatedObjects parameter
4647
if (relatedObjects.includes('lwc')) {
47-
migrationTools.push(this.createLWCComponentMigrationTool(this.namespace, this.org));
48+
migrationTools.push(this.createLWCComponentMigrationTool(this.namespace, projectDirectory));
4849
}
4950
if (relatedObjects.includes('labels')) {
5051
migrationTools.push(this.createCustomLabelMigrationTool(this.namespace, this.org));
@@ -75,9 +76,9 @@ export default class OmnistudioRelatedObjectMigrationFacade {
7576
}
7677

7778
// Factory methods to create instances of specific tools
78-
private createLWCComponentMigrationTool(namespace: string, org: Org): RelatedObjectsMigrate {
79+
private createLWCComponentMigrationTool(namespace: string, projectPath: string): LwcMigration {
7980
// Return an instance of LWCComponentMigrationTool when implemented
80-
throw new Error('LWCComponentMigrationTool implementation is not provided yet.');
81+
return new LwcMigration(projectPath, this.namespace, this.org);
8182
}
8283

8384
private createCustomLabelMigrationTool(namespace: string, org: Org): RelatedObjectsMigrate {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* eslint-disable @typescript-eslint/member-ordering */
2+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
3+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
4+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
5+
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
6+
import * as shell from 'shelljs';
7+
import { Org } from '@salesforce/core';
8+
import { fileutil, File } from '../../utils/file/fileutil';
9+
import { MigrationResult } from '../interfaces';
10+
import { sfProject } from '../../utils/sfcli/project/sfProject';
11+
import { JavaScriptParser } from '../../utils/lwcparser/jsParser/JavaScriptParser';
12+
import { HTMLParser } from '../../utils/lwcparser/htmlParser/HTMLParser';
13+
import { XmlParser } from '../../utils/lwcparser/xmlParser/XmlParser';
14+
import { Logger } from '../../utils/logger';
15+
import { BaseRelatedObjectMigration } from './BaseRealtedObjectMigration';
16+
17+
const LWC_DIR_PATH = '/force-app/main/default/lwc';
18+
const LWCTYPE = 'LightningComponentBundle';
19+
const XML_TAG_TO_REMOVE = 'runtimeNamespace';
20+
21+
export class LwcMigration extends BaseRelatedObjectMigration {
22+
public identifyObjects(migrationResults: MigrationResult[]): Promise<JSON[]> {
23+
throw new Error('Method not implemented.');
24+
}
25+
public migrateRelatedObjects(migrationResults: MigrationResult[], migrationCandidates: JSON[]): void {
26+
this.migrate();
27+
}
28+
// eslint-disable-next-line @typescript-eslint/member-ordering
29+
30+
public migrate(): void {
31+
const pwd = shell.pwd();
32+
shell.cd(this.projectPath);
33+
const targetOrg: Org = this.org;
34+
sfProject.retrieve(LWCTYPE, targetOrg.getUsername());
35+
this.processLwcFiles(this.projectPath);
36+
sfProject.deploy(LWCTYPE, targetOrg.getUsername());
37+
shell.cd(pwd);
38+
}
39+
40+
public processLwcFiles(dir: string): File[] {
41+
dir += LWC_DIR_PATH;
42+
let files: File[] = [];
43+
try {
44+
files = fileutil.readAllFiles(dir);
45+
this.processFile(files);
46+
} catch (error) {
47+
Logger.logger.error('Error in reading files', error);
48+
}
49+
return files;
50+
}
51+
52+
private processFile(files: File[]) {
53+
try {
54+
for (const file of files) {
55+
Logger.logger.info(file.location + ' files is Processing');
56+
if (file.ext === '.js') {
57+
this.processJavascriptFile(file);
58+
} else if (file.ext === '.html') {
59+
this.processHtmlFile(file);
60+
} else if (file.ext === '.xml') {
61+
this.processXMLFile(file);
62+
}
63+
}
64+
} catch (error) {
65+
Logger.logger.error(error.message);
66+
}
67+
}
68+
69+
processJavascriptFile(file: File): void {
70+
const jsParser = new JavaScriptParser();
71+
const filePath = file.location;
72+
const output = jsParser.replaceImportSource(filePath, this.namespace);
73+
jsParser.saveToFile(filePath, output);
74+
}
75+
76+
processHtmlFile(file: File): void {
77+
const filePath: string = file.location;
78+
const parse = new HTMLParser(filePath);
79+
parse.replaceTags(this.namespace);
80+
parse.saveToFile(filePath);
81+
}
82+
83+
processXMLFile(file: File): void {
84+
const filePath: string = file.location;
85+
const parser = new XmlParser(filePath);
86+
const xmlString = parser.removeNode(XML_TAG_TO_REMOVE);
87+
parser.saveToFile(filePath, xmlString);
88+
}
89+
}

src/utils/file/fileutil.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,32 @@ export class fileutil {
1515
});
1616
return files;
1717
}
18+
19+
public static readAllFiles(dirPath: string, fileList: File[] = []): File[] {
20+
// Read the directory contents
21+
const files = fs.readdirSync(dirPath);
22+
23+
files.forEach((filename) => {
24+
// Construct the full file/directory path
25+
const filePath = path.join(dirPath, filename);
26+
27+
// Check if the current path is a directory or a file
28+
if (fs.statSync(filePath).isDirectory()) {
29+
// If it's a directory, recurse into it
30+
fileutil.readAllFiles(filePath, fileList);
31+
} else {
32+
const name = path.parse(filename).name;
33+
const ext = path.parse(filename).ext;
34+
const filepath = path.resolve(dirPath, filename);
35+
const stat = fs.statSync(filepath);
36+
const isFile = stat.isFile();
37+
// If it's a file, add it to the fileList
38+
if (isFile) fileList.push(new File(name, filepath, ext));
39+
}
40+
});
41+
42+
return fileList;
43+
}
1844
}
1945

2046
export class File {

src/utils/lwcparser/htmlParser/HTMLParser.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import * as fs from 'fs';
66
import * as cheerio from 'cheerio';
77

8-
class HTMLParser {
8+
const DEFAULT_NAMESPACE = 'c';
9+
const TAG = 'tag';
10+
11+
export class HTMLParser {
912
private parser: cheerio.CheerioAPI;
1013

1114
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility
@@ -26,11 +29,27 @@ class HTMLParser {
2629
}
2730

2831
// Method to replace custom tags
29-
public replaceCustomTag(oldTag: string, newTag: string): void {
30-
this.parser(oldTag).each((_, element) => {
31-
const newElement = this.parser(`<${newTag}></${newTag}>`).html(this.parser(element).html());
32-
this.parser(element).replaceWith(newElement);
32+
public replaceTags(namespaceTag: string): string {
33+
// Load the HTML into cheerio
34+
const $ = this.parser;
35+
36+
// Find all tags that contain the substring "omnistudio" in their tag name
37+
$('*').each((i, element) => {
38+
if (element.type === TAG && element.name && element.name.includes(namespaceTag + '-')) {
39+
// Create a new tag with the same content and attributes as the old tag
40+
const newTag = DEFAULT_NAMESPACE + element.name.substring(element.name.indexOf('-'));
41+
const newElement = $(`<${newTag}>`).html($(element).html());
42+
43+
// Copy all attributes from the old element to the new one
44+
Object.keys(element.attribs).forEach((attr) => {
45+
newElement.attr(attr, $(element).attr(attr));
46+
});
47+
48+
// Replace the old element with the new one
49+
$(element).replaceWith(newElement);
50+
}
3351
});
52+
return $.html();
3453
}
3554

3655
// Method to save modified HTML back to a file
@@ -50,5 +69,3 @@ class HTMLParser {
5069
return this.parser.html();
5170
}
5271
}
53-
54-
export default HTMLParser;

src/utils/lwcparser/jsParser/JavaScriptParser.ts

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,53 @@
1+
/* eslint-disable @typescript-eslint/restrict-template-expressions */
12
/* eslint-disable @typescript-eslint/no-unsafe-call */
2-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
3+
/* eslint-disable @typescript-eslint/no-unsafe-return */
4+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
35
/* eslint-disable no-console */
4-
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
5-
// import * as fs from 'fs';
6-
// import { parse, type ParseResult } from '@babel/parser'; // Import all types from @babel/types
6+
import * as fs from 'fs';
7+
import * as parser from '@babel/parser';
8+
import traverse from '@babel/traverse';
9+
import generate from '@babel/generator';
10+
import * as t from '@babel/types';
711

8-
class JavaScriptParser {
9-
// private fileContent: string;
10-
private ast: File | null = null; // Specify the generic type argument
12+
const DEFAULT_NAMESPACE = 'c';
1113

12-
constructor(filePath: string) {
13-
// this.fileContent = fs.readFileSync(filePath, 'utf-8');
14-
this.ast = null;
15-
}
14+
export class JavaScriptParser {
15+
// Function to replace strings in import declarations and write back to file
16+
public replaceImportSource(filePath: string, oldSource: string): string {
17+
// Read the JavaScript file
18+
const code = fs.readFileSync(filePath, 'utf-8');
1619

17-
// public parseCode(): void {
18-
// const parseResult: File = parse(this.fileContent, {
19-
// sourceType: 'module', // Use 'script' if you're parsing non-module code
20-
// plugins: ['jsx', 'typescript'], // Add plugins as needed
21-
// });
20+
// Parse the code into an AST (Abstract Syntax Tree)
21+
const ast = parser.parse(code, {
22+
sourceType: 'module', // Specify that we are parsing an ES module
23+
plugins: ['decorators'], // Include any relevant plugins if necessary (e.g., 'jsx', 'flow', etc.)
24+
});
2225

23-
// if (parseResult.type === 'File') {
24-
// this.ast = parseResult;
25-
// } else {
26-
// throw new Error("Parsing did not return a 'File' node as expected.");
27-
// }
28-
// }
26+
// Traverse the AST and modify import declarations
27+
traverse(ast, {
28+
ImportDeclaration(path) {
29+
const importSource = path.node.source.value;
2930

30-
// Method to get the AST as a string
31-
getAST(): string | null {
32-
if (!this.ast) {
33-
console.error('AST is not available. Please parse the code first.');
34-
return null;
35-
}
36-
return JSON.stringify(this.ast, null, 2);
31+
// Check if the import source contains the old substring
32+
if (importSource.includes(oldSource + '/')) {
33+
// Replace the old substring with the new substring
34+
const updatedSource = importSource.replace(oldSource, DEFAULT_NAMESPACE);
35+
// Update the AST with the new source
36+
path.node.source = t.stringLiteral(updatedSource);
37+
}
38+
},
39+
});
40+
return generate(ast, {}, code).code;
3741
}
3842

39-
// Main method to process the file
40-
processFile(): void {
41-
// this.parseCode(); // Parse the JavaScript code
42-
const astString = this.getAST(); // Get the AST as a string
43-
if (astString) {
44-
console.log(astString); // Output the AST
43+
// Method to save modified HTML back to a file
44+
public saveToFile(filePath: string, output: string): void {
45+
try {
46+
fs.writeFileSync(filePath, output, 'utf-8');
47+
console.log(`Replaced import 'oldSource' with 'c' in file: ${filePath}`);
48+
} catch (error) {
49+
console.error(`Error writing file to disk: ${error}`);
50+
throw error;
4551
}
4652
}
4753
}
48-
49-
export default JavaScriptParser;
Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,61 @@
1-
/* eslint-disable @typescript-eslint/no-inferrable-types */
2-
/* eslint-disable @typescript-eslint/no-unsafe-return */
1+
/* eslint-disable @typescript-eslint/no-unsafe-call */
32
/* eslint-disable @typescript-eslint/member-ordering */
43
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
5-
/* eslint-disable @typescript-eslint/no-unsafe-call */
64
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
75
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
6+
import * as fs from 'fs';
87
import { DOMParser, XMLSerializer } from 'xmldom';
98

109
export class XmlParser {
1110
private xmlDoc: Document | null = null;
11+
private fileContent: string;
1212

13-
constructor(private xmlString: string) {
14-
this.parseXml();
13+
constructor(private filePath: string) {
14+
this.fileContent = fs.readFileSync(this.filePath, 'utf-8');
15+
this.parseXml(this.fileContent);
1516
}
1617

17-
private parseXml(): void {
18+
private parseXml(fileContent): void {
1819
const parser = new DOMParser();
19-
this.xmlDoc = parser.parseFromString(this.xmlString, 'text/xml');
20+
try {
21+
this.xmlDoc = parser.parseFromString(fileContent, 'text/xml');
22+
} catch (error) {
23+
throw new Error('Error in xml parsing');
24+
}
2025
}
2126

22-
public removeNode(tagName: string, index: number = 0): void {
27+
public removeNode(tagName: string, index = 0): string {
2328
if (!this.xmlDoc) {
2429
throw new Error('XML document has not been parsed.');
2530
}
26-
2731
const nodes = this.xmlDoc.getElementsByTagName(tagName);
2832

2933
if (nodes.length > index) {
3034
const nodeToRemove = nodes[index];
3135
nodeToRemove.parentNode?.removeChild(nodeToRemove);
32-
} else {
33-
throw new Error(`No node found with tag name "${tagName}" at index ${index}.`);
36+
37+
const serializer = new XMLSerializer();
38+
const xmlString: string = serializer.serializeToString(this.xmlDoc);
39+
return xmlString;
40+
// this.printResult(this.filePath, tagName, index);
3441
}
3542
}
3643

37-
public getXmlString(): string {
38-
if (!this.xmlDoc) {
39-
throw new Error('XML document has not been parsed.');
44+
public saveToFile(outputFilePath: string, xmlString: string): void {
45+
try {
46+
fs.writeFileSync(outputFilePath, xmlString);
47+
// eslint-disable-next-line no-console
48+
console.log(`Modified HTML saved to ${outputFilePath}`);
49+
} catch (error) {
50+
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
51+
console.error(`Error writing file to disk: ${error}`);
52+
throw error;
4053
}
41-
42-
const serializer = new XMLSerializer();
43-
return serializer.serializeToString(this.xmlDoc);
4454
}
55+
56+
// protected printResult(fileName: string, xmlTag: string, lineNumber: number): void {
57+
// console.log('FileName :' + fileName);
58+
// console.log('Tag to remove :' + xmlTag);
59+
// console.log('Line Number :', lineNumber);
60+
// }
4561
}

0 commit comments

Comments
 (0)