Skip to content

Commit 04ca088

Browse files
committed
feat: add file system utilities for 'upgrade' process
'fs-promise.ts' is a utility file for asynchronous read/write operations 'change.ts' implements some change interfaces as specified by the upgrade design doc linked below: https://github.com/hansl/angular-cli/blob/7ea3e78ff3d899d5277aac5dfeeece4056d0efe3/docs/design/upgrade.md
1 parent 1690a82 commit 04ca088

2 files changed

Lines changed: 238 additions & 0 deletions

File tree

addon/ng2/utilities/change.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict';
2+
3+
import * as Promise from 'ember-cli/lib/ext/promise';
4+
let fs = require('fs');
5+
6+
const readFile = Promise.denodeify(fs.readFile);
7+
const writeFile = Promise.denodeify(fs.writeFile);
8+
9+
export interface Change {
10+
/**
11+
* True on success, false otherwise.
12+
*/
13+
apply(): Promise<void>;
14+
15+
// The file this change should be applied to. Some changes might not apply to
16+
// a file (maybe the config).
17+
path: string | null;
18+
19+
// The order this change should be applied. Normally the position inside the file.
20+
// Changes are applied from the bottom of a file to the top.
21+
order: number | null;
22+
23+
// The description of this change. This will be outputted in a dry or verbose run.
24+
description: string;
25+
}
26+
27+
28+
/**
29+
* Will add text to the source code.
30+
*/
31+
export class InsertChange implements Change {
32+
const order: number;
33+
const description: string;
34+
constructor(
35+
public path: string,
36+
private pos: number,
37+
private toAdd: string,
38+
) {
39+
this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;
40+
this.order = pos;
41+
}
42+
43+
/**
44+
* This method does not insert spaces if there is none in the original string.
45+
* @param file (path to file)
46+
* @param pos
47+
* @param toAdd (text to add)
48+
* @return Promise with a description on success or reject on error
49+
*/
50+
apply(): Promise<any> {
51+
return readFile(this.path, 'utf8').then(content => {
52+
content = content.substring(0, this.pos) + this.toAdd + content.substring(this.pos);
53+
return writeFile(this.path, content);
54+
});
55+
}
56+
}
57+
58+
/**
59+
* Will remove text from the source code.
60+
*/
61+
export class RemoveChange implements Change {
62+
const order: number;
63+
const description: string;
64+
65+
constructor(
66+
public path: string,
67+
private pos: number,
68+
private toRemove: string) {
69+
this.description = `Removed ${toRemove} into position ${pos} of ${path}`;
70+
this.order = pos;
71+
}
72+
73+
apply(): Promise<any> {
74+
return readFile(this.path, 'utf8').then(content => {
75+
content = content.substring(0, this.pos) + content.substring(this.pos + this.toRemove.length);
76+
return writeFile(this.path, content);
77+
});
78+
}
79+
}
80+
81+
/**
82+
* Will replace text from the source code.
83+
*/
84+
export class ReplaceChange implements Change {
85+
const order: number;
86+
const description: string;
87+
88+
constructor(
89+
public path: string,
90+
private pos: number,
91+
private oldText: string,
92+
private newText: string) {
93+
this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;
94+
this.order = pos;
95+
}
96+
97+
apply(): Promise<any> {
98+
return readFile(this.path, 'utf8').then(content => {
99+
content = content.substring(0, this.pos) + this.newText + content.substring(this.pos + this.oldText.length);
100+
writeFile(this.path, content);
101+
});
102+
}
103+
}

tests/acceptance/change.spec.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
let path = require('path');
5+
let mockFs = require('mock-fs');
6+
let change = require('../../addon/ng2/utilities/change');
7+
let fs = require('fs');
8+
let Promise = require('ember-cli/lib/ext/promise');
9+
10+
11+
12+
describe('Change: Testing operations', () => {
13+
const readFile = Promise.denodeify(fs.readFile);
14+
let sourcePath = 'src/app/my-component';
15+
16+
beforeEach(() => {
17+
let mockDrive = {
18+
'src/app/my-component': {
19+
'add-file.txt': 'hello',
20+
'remove-replace-file.txt': 'import * as foo from "./bar"',
21+
'replace-file.txt': 'import { FooComponent } from "./baz"'
22+
}
23+
}
24+
mockFs(mockDrive);
25+
});
26+
27+
afterEach(() => {
28+
mockFs.restore();
29+
});
30+
31+
describe('InsertChange: add string to the source code', () => {
32+
33+
it('should add text to the source code', () => {
34+
let sourceFile = path.join(sourcePath, 'add-file.txt');
35+
expect(fs.existsSync(sourceFile)).to.equal(true);
36+
let changeInstance = new change.InsertChange(sourceFile, 6, ' world!');
37+
return changeInstance
38+
.apply()
39+
.then(() => readFile(sourceFile, 'utf8'))
40+
.then(contents => {
41+
expect(contents).to.equal('hello world!');
42+
})
43+
})
44+
45+
it('should append string before any strings in the source code, and hence unexpected output', () => {
46+
let sourceFile = path.join(sourcePath, 'add-file.txt');
47+
expect(fs.existsSync(sourceFile)).to.equal(true);
48+
let changeInstance = new change.InsertChange(sourceFile, -6, ' world!');
49+
return changeInstance
50+
.apply()
51+
.then(() => readFile(sourceFile))
52+
.then(contents => {
53+
expect(contents).to.not.equal('hello world!');
54+
});
55+
})
56+
57+
it('should add nothing in the source code if empty string is inserted', () => {
58+
let sourceFile = path.join(sourcePath, 'add-file.txt');
59+
expect(fs.existsSync(sourceFile)).to.equal(true);
60+
let changeInstance = new change.InsertChange(sourceFile, 6, '');
61+
return changeInstance
62+
.apply()
63+
.then(() => readFile(sourceFile, 'utf8'))
64+
.then(contents => {
65+
expect(contents).to.equal('hello');
66+
});
67+
})
68+
});
69+
70+
describe('RemoveChange: remove string from the source code', () => {
71+
72+
it('should remove given text from the source code', () => {
73+
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
74+
expect(fs.existsSync(sourceFile)).to.equal(true);
75+
let changeInstance = new change.RemoveChange(sourceFile, 9, 'as foo');
76+
return changeInstance
77+
.apply()
78+
.then(() => readFile(sourceFile, 'utf8'))
79+
.then(contents => {
80+
expect(contents).to.equal('import * from "./bar"');
81+
});
82+
})
83+
84+
it('should remove nothing in the source code if empty string is to be removed', () => {
85+
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
86+
expect(fs.existsSync(sourceFile)).to.equal(true);
87+
let changeInstance = new change.RemoveChange(sourceFile, 9, '');
88+
return changeInstance
89+
.apply()
90+
.then(() => readFile(sourceFile, 'utf8'))
91+
.then(contents => {
92+
expect(contents).to.equal('import * as foo from "./bar"');
93+
});
94+
})
95+
});
96+
97+
describe('ReplaceChange: replace string in the source code', () => {
98+
99+
it('should replace a given text in the source code', () => {
100+
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
101+
expect(fs.existsSync(sourceFile)).to.equal(true);
102+
let changeInstance = new change.ReplaceChange(sourceFile, 7, '* as foo', '{ fooComponent }');
103+
return changeInstance
104+
.apply()
105+
.then(() => readFile(sourceFile, 'utf8'))
106+
.then(contents => {
107+
expect(contents).to.equal('import { fooComponent } from "./bar"');
108+
});
109+
})
110+
111+
it('should add string to the position of an empty string', () => {
112+
let sourceFile = path.join(sourcePath, 'replace-file.txt');
113+
expect(fs.existsSync(sourceFile)).to.equal(true);
114+
let changeInstance = new change.ReplaceChange(sourceFile, 9, '', 'BarComponent, ');
115+
return changeInstance
116+
.apply()
117+
.then(() => readFile(sourceFile, 'utf8'))
118+
.then(contents => {
119+
expect(contents).to.equal('import { BarComponent, FooComponent } from "./baz"');
120+
});
121+
})
122+
123+
it('should removes the string in the given postion and add nothing if empty string is to be replaced', () => {
124+
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
125+
expect(fs.existsSync(sourceFile)).to.equal(true);
126+
let changeInstance = new change.ReplaceChange(sourceFile, 9, ' as foo', '');
127+
return changeInstance
128+
.apply()
129+
.then(() => readFile(sourceFile, 'utf8'))
130+
.then(contents => {
131+
expect(contents).to.equal('import * from "./bar"');
132+
});
133+
})
134+
});
135+
});

0 commit comments

Comments
 (0)