Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions addon/ng2/utilities/change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict';
Copy link
Copy Markdown
Contributor

@hansl hansl Jun 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking this file should probably be under /lib/refactor.

Copy link
Copy Markdown
Contributor Author

@ashoktamang ashoktamang Jun 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this once the design of the refactoring system becomes clearer. --alex


import * as Promise from 'ember-cli/lib/ext/promise';
import fs = require('fs');

const readFile = Promise.denodeify(fs.readFile);
const writeFile = Promise.denodeify(fs.writeFile);

export interface Change {

apply(): Promise<void>;

// The file this change should be applied to. Some changes might not apply to
// a file (maybe the config).
path: string | null;

// The order this change should be applied. Normally the position inside the file.
// Changes are applied from the bottom of a file to the top.
order: number;

// The description of this change. This will be outputted in a dry or verbose run.
description: string;
}

/**
* Will add text to the source code.
*/
export class InsertChange implements Change {

const order: number;
const description: string;

constructor(
public path: string,
private pos: number,
private toAdd: string,
) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;
this.order = pos;
}

/**
* This method does not insert spaces if there is none in the original string.
*/
apply(): Promise<any> {
return readFile(this.path, 'utf8').then(content => {
let prefix = content.substring(0, this.pos);
let suffix = content.substring(this.pos);
return writeFile(this.path, `${prefix}${this.toAdd}${suffix}`);
});
}
}

/**
* Will remove text from the source code.
*/
export class RemoveChange implements Change {

const order: number;
Copy link
Copy Markdown
Member

@alxhub alxhub Jun 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's a duplicate of the constructor parameter.

const description: string;

constructor(
public path: string,
private pos: number,
private toRemove: string) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Removed ${toRemove} into position ${pos} of ${path}`;
this.order = pos;
}

apply(): Promise<any> {
return readFile(this.path, 'utf8').then(content => {
let prefix = content.substring(0, this.pos);
let suffix = content.substring(this.pos + this.toRemove.length);
// TODO: throw error if toRemove doesn't match removed string.
return writeFile(this.path, `${prefix}${suffix}`);
});
}
}

/**
* Will replace text from the source code.
*/
export class ReplaceChange implements Change {

const order: number;
const description: string;

constructor(
public path: string,
private pos: number,
private oldText: string,
private newText: string) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;
this.order = pos;
}

apply(): Promise<any> {
return readFile(this.path, 'utf8').then(content => {
let prefix = content.substring(0, this.pos);
let suffix = content.substring(this.pos + this.oldText.length);
// TODO: throw error if oldText doesn't match removed string.
return writeFile(this.path, `${prefix}${this.newText}${suffix}`);
});
}
}
120 changes: 120 additions & 0 deletions tests/acceptance/change.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

// This needs to be first so fs module can be mocked correctly.
let mockFs = require('mock-fs');

import {expect} from 'chai';
import {InsertChange, RemoveChange, ReplaceChange} from '../../addon/ng2/utilities/change';
import fs = require('fs');

let path = require('path');
let Promise = require('ember-cli/lib/ext/promise');

const readFile = Promise.denodeify(fs.readFile);

describe('Change', () => {
let sourcePath = 'src/app/my-component';

beforeEach(() => {
let mockDrive = {
'src/app/my-component': {
'add-file.txt': 'hello',
'remove-replace-file.txt': 'import * as foo from "./bar"',
'replace-file.txt': 'import { FooComponent } from "./baz"'
}
};
mockFs(mockDrive);
});
afterEach(() => {
mockFs.restore();
});

describe('InsertChange', () => {
let sourceFile = path.join(sourcePath, 'add-file.txt');

it('adds text to the source code', () => {
let changeInstance = new InsertChange(sourceFile, 6, ' world!');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('hello world!');
});
});
it('fails for negative position', () => {
expect(() => new InsertChange(sourceFile, -6, ' world!')).to.throw(Error);
});
it('adds nothing in the source code if empty string is inserted', () => {
let changeInstance = new InsertChange(sourceFile, 6, '');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('hello');
});
});
});

describe('RemoveChange', () => {
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');

it('removes given text from the source code', () => {
let changeInstance = new RemoveChange(sourceFile, 9, 'as foo');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('import * from "./bar"');
});
});
it('fails for negative position', () => {
expect(() => new RemoveChange(sourceFile, -6, ' world!')).to.throw(Error);
});
it('does not change the file if told to remove empty string', () => {
let changeInstance = new RemoveChange(sourceFile, 9, '');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('import * as foo from "./bar"');
});
});
});

describe('ReplaceChange', () => {
it('replaces the given text in the source code', () => {
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
let changeInstance = new ReplaceChange(sourceFile, 7, '* as foo', '{ fooComponent }');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('import { fooComponent } from "./bar"');
});
});
it('fails for negative position', () => {
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
expect(() => new ReplaceChange(sourceFile, -6, 'hello', ' world!')).to.throw(Error);
});
it('adds string to the position of an empty string', () => {
let sourceFile = path.join(sourcePath, 'replace-file.txt');
let changeInstance = new ReplaceChange(sourceFile, 9, '', 'BarComponent, ');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('import { BarComponent, FooComponent } from "./baz"');
});
});
it('removes the given string only if an empty string to add is given', () => {
let sourceFile = path.join(sourcePath, 'remove-replace-file.txt');
let changeInstance = new ReplaceChange(sourceFile, 9, ' as foo', '');
return changeInstance
.apply()
.then(() => readFile(sourceFile, 'utf8'))
.then(contents => {
expect(contents).to.equal('import * from "./bar"');
});
});
});
});