-
Notifications
You must be signed in to change notification settings - Fork 11.9k
feat: add file system utilities for 'upgrade' process #1088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+234
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| 'use strict'; | ||
|
|
||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}`); | ||
| }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"'); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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