├── test ├── files │ ├── filtering │ │ ├── file1.txt │ │ ├── file2.js │ │ ├── .dir-to-ignore │ │ │ └── file1.js │ │ ├── foo_file2.js │ │ ├── bar_file1.jsx │ │ ├── foo_file1.jsx │ │ └── file1.js │ ├── outside-cwd │ │ └── file1.js │ ├── html │ │ └── original │ │ │ ├── css │ │ │ └── file1.css │ │ │ ├── file1.html │ │ │ ├── level1 │ │ │ └── file2.js │ │ │ ├── file with spaces in name.js │ │ │ ├── images │ │ │ └── file1.png │ │ │ └── index.html │ ├── modules │ │ └── original │ │ │ ├── file1.conf │ │ │ ├── file1.html │ │ │ ├── file1.json │ │ │ ├── file1.txt │ │ │ ├── file2.js │ │ │ ├── file1.js.bak │ │ │ ├── rootfile1.js │ │ │ ├── rootfile3.js │ │ │ ├── rootfile4.js │ │ │ ├── level1a │ │ │ ├── file3.js │ │ │ ├── file3.jsx │ │ │ ├── file5.js │ │ │ ├── level2a │ │ │ │ ├── file6.js │ │ │ │ └── level3a │ │ │ │ │ ├── file2.txt │ │ │ │ │ └── file8.js │ │ │ └── file-with-references.jsx │ │ │ ├── level1b │ │ │ ├── file1.css │ │ │ ├── file4.js │ │ │ └── level2b │ │ │ │ ├── file2.jsx │ │ │ │ ├── file7.js │ │ │ │ └── file2.json │ │ │ ├── rootfile with spaces in name 2.js │ │ │ └── file-with-references.js │ ├── ref-outside-cwd │ │ └── referencing-file.js │ └── .reffixrc ├── fixtures │ ├── htmlFilesToMove.json │ └── moduleFilesToMove.json ├── OutsideWcdSpec.js ├── ReportSpec.js ├── helpers │ └── fileMover.js ├── FixedHtmlFileSpec.js ├── FixedModuleFileSpec.js ├── OriginalHtmlFilesSpec.js ├── MovedHTMLFileSpec.js ├── MovedModuleFileSpec.js ├── ConfigSpec.js ├── FilteringSpec.js └── OriginalModuleFilesSpec.js ├── img ├── example1.png └── example2.png ├── index.js ├── CHANGELOG.md ├── lib ├── qExtensions.js ├── gitParser.js ├── prompter.js ├── utils.js ├── updater.js ├── config.js ├── reporter.js ├── cli.js └── parser.js ├── LICENSE ├── config.json ├── package.json └── README.md /test/files/filtering/file1.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/filtering/file2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/outside-cwd/file1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/html/original/css/file1.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/html/original/file1.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file1.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file1.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file1.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file1.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/html/original/level1/file2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/file1.js.bak: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/rootfile1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/rootfile3.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/rootfile4.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/filtering/.dir-to-ignore/file1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1a/file3.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1a/file3.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1a/file5.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1b/file1.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1b/file4.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/html/original/file with spaces in name.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1a/level2a/file6.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1b/level2b/file2.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1b/level2b/file7.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1b/level2b/file2.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/filtering/foo_file2.js: -------------------------------------------------------------------------------- 1 | import module1 from './file1'; -------------------------------------------------------------------------------- /test/files/modules/original/level1a/level2a/level3a/file2.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/level1a/level2a/level3a/file8.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/modules/original/rootfile with spaces in name 2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/files/filtering/bar_file1.jsx: -------------------------------------------------------------------------------- 1 | import module1 from './file1'; -------------------------------------------------------------------------------- /test/files/filtering/foo_file1.jsx: -------------------------------------------------------------------------------- 1 | import module1 from './file1'; -------------------------------------------------------------------------------- /img/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkendall/reffix/HEAD/img/example1.png -------------------------------------------------------------------------------- /img/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkendall/reffix/HEAD/img/example2.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var cli = require('./lib/cli'); 4 | 5 | cli.start(); -------------------------------------------------------------------------------- /test/files/ref-outside-cwd/referencing-file.js: -------------------------------------------------------------------------------- 1 | import module1 from '../outside-cwd/file1.js'; -------------------------------------------------------------------------------- /test/files/modules/original/level1a/file-with-references.jsx: -------------------------------------------------------------------------------- 1 | import module from '../level1b/file4'; -------------------------------------------------------------------------------- /test/fixtures/htmlFilesToMove.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "index.html", 4 | "level1/index.html" 5 | ] 6 | ] -------------------------------------------------------------------------------- /test/files/filtering/file1.js: -------------------------------------------------------------------------------- 1 | import module1 from './foo_file1.jsx'; 2 | import module1 from './bar_file1.jsx'; -------------------------------------------------------------------------------- /test/files/html/original/images/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkendall/reffix/HEAD/test/files/html/original/images/file1.png -------------------------------------------------------------------------------- /test/files/.reffixrc: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "referencingFileFilter": [ 4 | "test.*" 5 | ], 6 | "referencedFileFilter": [ 7 | "test_test.*" 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.1.1 (2016-07-27) 2 | 3 | - By default exclude comments from HTML files 4 | 5 | ### v0.1.2 (2016-07-29) 6 | 7 | - Improved accuracy of handling duplicate filenames when using git status. -------------------------------------------------------------------------------- /lib/qExtensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | 5 | // Extend Q to handle errors in a consistent way 6 | Q.makePromise.prototype.handleError = function() { 7 | return this.catch(function(err) { 8 | console.error(err.stack); 9 | return err; 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/moduleFilesToMove.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "file-with-references.js", 4 | "level1c/level2c/file-with-references.js" 5 | ], 6 | [ 7 | "level1b/file4.js", 8 | "level1a/file4.js" 9 | ], 10 | [ 11 | "level1b/level2b/file7.js", 12 | "file7.js" 13 | ] 14 | ] -------------------------------------------------------------------------------- /test/files/html/original/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | link 13 | image 14 | 15 | -------------------------------------------------------------------------------- /test/OutsideWcdSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var config = require('../lib/config'); 5 | var parser = require('../lib/parser'); 6 | 7 | describe('Outside CWD', function() { 8 | 9 | var testWorkingDir = 'test/files/ref-outside-cwd'; 10 | 11 | describe('References', function() { 12 | var files; 13 | var options = { 14 | workingDirectory: testWorkingDir 15 | }; 16 | beforeEach(function(done) { 17 | config.reset(); 18 | config.forceSet(options); 19 | parser.getReferences().then(function(result) { 20 | files = result; 21 | done(); 22 | }); 23 | }); 24 | it('Should find file outside of CWD', function() { 25 | expect(Object.keys(files.referencedFiles)).to.have.length(1); 26 | expect(files.referencedFiles).to.have.property(path.join(process.cwd(), 'test/files/outside-cwd/file1.js')); 27 | }); 28 | 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /test/ReportSpec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var config = require('../lib/config'); 4 | var parser = require('../lib/parser'); 5 | var reporter = require('../lib/reporter'); 6 | 7 | describe('Reports', function() { 8 | var filesCheckedReport; 9 | var referencingFilesReport; 10 | var options = { 11 | workingDirectory: 'test/files/modules/original', 12 | referencingFileFilter: ['*.js'], 13 | referencedFileFilter: ['*.js'] 14 | }; 15 | beforeEach(function(done) { 16 | config.forceSet(options); 17 | parser.getAll() 18 | .then(function(fileData) { 19 | filesCheckedReport = reporter.reportFilesChecked(fileData); 20 | var reportOptions = {referencingFiles: true}; 21 | referencingFilesReport = reporter.getReport(fileData, reportOptions); 22 | done(); 23 | }); 24 | }); 25 | it('Should report correct number of files checked', function() { 26 | expect(filesCheckedReport).to.contain('12 files'); 27 | }); 28 | 29 | }); -------------------------------------------------------------------------------- /test/files/modules/original/file-with-references.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import module1 from './level1a/file-with-references.jsx'; 4 | import $ from './file2.js'; 5 | 6 | /* 7 | * import module1 from './file12.js'; 8 | * 9 | */ 10 | 11 | import { member1, member2 } from './level1a/level2a/file6'; 12 | import * as name from "./rootfile1"; 13 | import defaultMember, { member1, member2 } 14 | from "./rootfile with spaces in name 2"; 15 | import defaultMember, * as name from "./rootfile3"; 16 | import './rootfile4.js' 17 | // import module2a from './file12.js'; 18 | 19 | 20 | var module3 = require('./level1a/file3.jsx'); 21 | const module4 = require('./level1b/file4.js'); 22 | const module5 = require ("./level1b/level2b/file2.jsx"); 23 | 24 | let module6 = require( './level1b/level2b/file7' ).someMethod; 25 | var module7 = require("./level1a/file5.js")(); 26 | 27 | // var module8 = require('./file10.js'); 28 | 29 | /* 30 | * import module9 from './file11.js'; 31 | * 32 | */ 33 | 34 | let foo = bar; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2016 Robert Kendall 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "workingDirectory": ".", 4 | "directoriesToExclude": [ 5 | ".*", 6 | "node_modules", 7 | "build" 8 | ], 9 | "referencingFileFilter": [ 10 | "*.js", 11 | "*.jsx" 12 | ], 13 | "referencedFileFilter": [ 14 | "*.*" 15 | ], 16 | "searchPatterns": [ 17 | "(^|\\s)import\\s+([\\w\\-\\{\\}\\,\\*\\$\\s]+\\s+)?(['\"]){{valuePattern}}\\3", 18 | "(^|[^\\w-])require *\\( *(['\"]){{valuePattern}}\\2 *\\)" 19 | ], 20 | "valuePattern": "\\.+/[^'\"]+", 21 | "currentDirectoryPrefix": "./", 22 | "textToExclude": [ 23 | "// *[^\\n]+\\n", 24 | "/\\*[\\s\\S]+?\\*/" 25 | ] 26 | }, 27 | "html": { 28 | "referencingFileFilter": [ 29 | "*.html", 30 | "*.js" 31 | ], 32 | "referencedFileFilter": [ 33 | "*.*" 34 | ], 35 | "searchPatterns": [ 36 | "]*href=(['\"]){{valuePattern}}\\1[^>]*>", 37 | "]*href=(['\"]){{valuePattern}}\\1[^>]*>", 38 | "'); 45 | expect(content).to.contain(''); 46 | expect(content).to.contain('link'); 47 | expect(content).to.contain('image'); 48 | done(); 49 | }); 50 | }) 51 | }); 52 | 53 | }); -------------------------------------------------------------------------------- /test/FixedModuleFileSpec.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var expect = require('chai').expect; 4 | var mv = require('mv'); 5 | 6 | var fileMover = require('./helpers/fileMover'); 7 | var config = require('../lib/config'); 8 | var updater = require('../lib/updater'); 9 | 10 | var filesToMove = require('./fixtures/moduleFilesToMove.json'); 11 | 12 | describe('File Repair for Modules', function() { 13 | var testBaseDir = 'test/files/modules/moved'; 14 | var testDir = 'modules'; 15 | var options = { 16 | workingDirectory: testBaseDir 17 | }; 18 | var baseDir = path.resolve(testBaseDir) + '/'; 19 | before(function(done) { 20 | fileMover.moveFiles(testDir, filesToMove, done); 21 | }); 22 | after(function(done) { 23 | fileMover.deleteMovedFiles(testDir, done); 24 | }); 25 | describe('References', function() { 26 | var files; 27 | var pathOfFileWithReferences = path.join(baseDir, 'level1c/level2c/file-with-references.js'); 28 | beforeEach(function(done) { 29 | config.forceSet(options); 30 | updater.fixReferences().then(function(result) { 31 | files = result; 32 | done(); 33 | }) 34 | }); 35 | it('Should fix correct files', function() { 36 | expect(files).to.have.length(2); 37 | expect(files[0]).to.equal(path.join(baseDir, 'level1a/file-with-references.jsx')); 38 | expect(files[1]).to.equal(pathOfFileWithReferences); 39 | }); 40 | it('Should correctly update file content', function(done) { 41 | fs.readFile(pathOfFileWithReferences, 'utf8', function(err, content) { 42 | expect(content).to.contain("import module1 from '../../level1a/file-with-references.jsx';"); 43 | expect(content).to.contain("const module4 = require('../../level1a/file4.js');"); 44 | expect(content).to.contain("let module6 = require( '../../file7.js' ).someMethod;"); 45 | done(); 46 | }); 47 | }) 48 | }); 49 | 50 | }); -------------------------------------------------------------------------------- /test/OriginalHtmlFilesSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var config = require('../lib/config'); 5 | var parser = require('../lib/parser'); 6 | 7 | describe('Original HTML Files', function() { 8 | 9 | var testWorkingDir = 'test/files/html/original'; 10 | 11 | describe('References', function() { 12 | var files; 13 | var options = { 14 | mode: 'html', 15 | workingDirectory: testWorkingDir 16 | }; 17 | beforeEach(function(done) { 18 | config.reset(); 19 | config.set(options); 20 | parser.getReferences().then(function(result) { 21 | files = result; 22 | done(); 23 | }); 24 | }); 25 | afterEach(function() { 26 | config.reset(); 27 | }); 28 | it('Should get correct number of files', function() { 29 | expect(Object.keys(files.existingFiles)).to.have.length(6); 30 | }); 31 | it('Should get correct number of referencing files', function() { 32 | expect(Object.keys(files.referencingFiles)).to.have.length(1); 33 | }); 34 | it('Should get correct file paths', function() { 35 | expect(files.referencingFiles).to.have.property(path.resolve(options.workingDirectory, 'index.html')); 36 | }); 37 | it('Should get correct number of references within files', function() { 38 | expect(Object.keys(files.referencedFiles)).to.have.length(5); 39 | }); 40 | it('Should get correct file paths', function() { 41 | expect(files.referencedFiles).to.have.property(path.resolve(options.workingDirectory, 'level1/file2.js')); 42 | }); 43 | }); 44 | 45 | describe('Filtered References for CSS only', function() { 46 | var files; 47 | var options = { 48 | mode: 'html', 49 | workingDirectory: testWorkingDir, 50 | referencedFileFilter: ['*.css'] 51 | }; 52 | beforeEach(function(done) { 53 | config.set(options); 54 | parser.getReferences().then(function(result) { 55 | files = result; 56 | done(); 57 | }) 58 | }); 59 | it('Should get correct number of references within files', function() { 60 | expect(Object.keys(files.referencedFiles)).to.have.length(1); 61 | }) 62 | }); 63 | 64 | describe('Broken References', function() { 65 | var brokenReferences; 66 | var options = { 67 | workingDirectory: testWorkingDir 68 | }; 69 | beforeEach(function(done) { 70 | config.forceSet(options); 71 | parser.getBrokenReferences().then(function(result) { 72 | brokenReferences = result.brokenReferences; 73 | done(); 74 | }) 75 | }); 76 | it('Should find no broken references', function() { 77 | expect(Object.keys(brokenReferences)).to.have.length(0); 78 | }); 79 | }); 80 | 81 | }); -------------------------------------------------------------------------------- /test/MovedHTMLFileSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var fileMover = require('./helpers/fileMover'); 5 | var config = require('../lib/config'); 6 | var parser = require('../lib/parser'); 7 | 8 | var filesToMove = require('./fixtures/htmlFilesToMove.json'); 9 | 10 | describe('Moved HMTL Files', function() { 11 | 12 | var testBaseDir = 'test/files/html/moved'; 13 | var testDir = 'html'; 14 | var fullBasePath = path.resolve(testBaseDir) + '/'; 15 | 16 | before(function(done) { 17 | fileMover.moveFiles(testDir, filesToMove, done); 18 | }); 19 | after(function(done) { 20 | fileMover.deleteMovedFiles(testDir, done); 21 | }); 22 | 23 | describe('Broken References', function() { 24 | var brokenReferences; 25 | var options = { 26 | mode: 'html', 27 | workingDirectory: testBaseDir 28 | }; 29 | beforeEach(function(done) { 30 | config.reset(); 31 | config.set(options); 32 | parser.getBrokenReferences(options) 33 | .then(function(result) { 34 | brokenReferences = result.brokenReferences; 35 | done(); 36 | }) 37 | }); 38 | afterEach(function() { 39 | config.reset(); 40 | }); 41 | it('Should find correct number of broken references', function() { 42 | expect(brokenReferences).to.have.length(5); 43 | }); 44 | 45 | it('Should correctly identify broken references, referencers, and correct paths', function() { 46 | 47 | var result1 = brokenReferences.find(function(brokenReference) { 48 | if (brokenReference.referencedFile === fullBasePath + 'level1/css/file1.css') { 49 | return true; 50 | } 51 | }); 52 | expect(result1).to.be.ok; 53 | expect(result1.referencingFiles).to.contain(fullBasePath + 'level1/index.html'); 54 | expect(result1.correctPath).to.equal(fullBasePath + 'css/file1.css'); 55 | 56 | var result2 = brokenReferences.find(function(brokenReference) { 57 | if (brokenReference.referencedFile.indexOf(fullBasePath + '' + 'level1/file1.html') !== -1) { 58 | return true; 59 | } 60 | }); 61 | expect(result2).to.be.ok; 62 | expect(result2.referencingFiles).to.contain(fullBasePath + 'level1/index.html'); 63 | expect(result2.correctPath).to.equal(fullBasePath + 'file1.html'); 64 | 65 | var result3 = brokenReferences.find(function(brokenReference) { 66 | if (brokenReference.referencedFile.indexOf(fullBasePath + 'level1/file with spaces in name.js') !== -1) { 67 | return true; 68 | } 69 | }); 70 | expect(result3).to.be.ok; 71 | expect(result3.referencingFiles).to.contain(fullBasePath + 'level1/index.html'); 72 | expect(result3.correctPath).to.equal(fullBasePath + 'file with spaces in name.js'); 73 | 74 | }); 75 | 76 | }); 77 | 78 | }); -------------------------------------------------------------------------------- /test/MovedModuleFileSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var fileMover = require('./helpers/fileMover'); 5 | var config = require('../lib/config'); 6 | var parser = require('../lib/parser'); 7 | 8 | var filesToMove = require('./fixtures/moduleFilesToMove.json'); 9 | 10 | describe('Moved Files', function() { 11 | 12 | var testBaseDir = 'test/files/modules/moved'; 13 | var testDir = 'modules'; 14 | var fullBasePath = path.resolve(testBaseDir) + '/'; 15 | 16 | before(function(done) { 17 | fileMover.moveFiles(testDir, filesToMove, done); 18 | }); 19 | after(function(done) { 20 | fileMover.deleteMovedFiles(testDir, done); 21 | }); 22 | 23 | describe('Broken References', function() { 24 | var brokenReferences; 25 | var options = { 26 | workingDirectory: testBaseDir 27 | }; 28 | beforeEach(function(done) { 29 | config.reset(); 30 | config.forceSet(options); 31 | parser.getBrokenReferences(options) 32 | .then(function(result) { 33 | brokenReferences = result.brokenReferences; 34 | done(); 35 | }) 36 | }); 37 | afterEach(function() { 38 | config.reset(); 39 | }); 40 | it('Should find correct number of broken references', function() { 41 | expect(brokenReferences).to.have.length(13); 42 | }); 43 | it('Should correctly identify broken references, referencers, and correct paths', function() { 44 | var result1 = brokenReferences.find(function(brokenReference) { 45 | if (brokenReference.referencedFile === fullBasePath + 'level1c/level2c/level1a/file-with-references.jsx') { 46 | return true; 47 | } 48 | }); 49 | expect(result1).to.be.ok; 50 | expect(result1.referencingFiles).to.contain(fullBasePath + 'level1c/level2c/file-with-references.js'); 51 | expect(result1.correctPath).to.equal(fullBasePath + 'level1a/file-with-references.jsx'); 52 | 53 | var result2 = brokenReferences.find(function(brokenReference) { 54 | if (brokenReference.referencedFile.indexOf(fullBasePath + 'level1c/level2c/level1b/file4.js') !== -1) { 55 | return true; 56 | } 57 | }); 58 | expect(result2).to.be.ok; 59 | expect(result2.referencingFiles).to.contain(fullBasePath + 'level1c/level2c/file-with-references.js'); 60 | expect(result2.correctPath).to.equal(fullBasePath + 'level1a/file4.js'); 61 | 62 | var result3 = brokenReferences.find(function(brokenReference) { 63 | if (brokenReference.referencedFile.indexOf(fullBasePath + 'level1c/level2c/level1b/level2b/file7.js') !== -1) { 64 | return true; 65 | } 66 | }); 67 | expect(result3).to.be.ok; 68 | expect(result3.referencingFiles).to.contain(fullBasePath + 'level1c/level2c/file-with-references.js'); 69 | expect(result3.correctPath).to.equal(fullBasePath + 'file7.js'); 70 | }); 71 | 72 | }); 73 | 74 | }); -------------------------------------------------------------------------------- /test/ConfigSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var config = require('../lib/config'); 5 | 6 | describe('Configuration', function() { 7 | 8 | describe('Should accept options', function() { 9 | 10 | beforeEach(function() { 11 | config.reset(); 12 | }); 13 | afterEach(function() { 14 | config.reset(); 15 | }); 16 | 17 | it('Should load correct value for working dir', function() { 18 | var workingDir = path.join(process.cwd(), 'test/files/modules'); 19 | var options = { 20 | workingDirectory: workingDir 21 | }; 22 | config.set(options); 23 | var settings = config.get(); 24 | expect(settings.workingDirectory).to.equal(workingDir); 25 | }); 26 | it('Should change modes correctly', function() { 27 | config.setMode('html'); 28 | var settings = config.get(); 29 | expect(settings.referencingFileFilter).to.have.length(2); 30 | expect(settings.referencingFileFilter).to.contain('*.html'); 31 | expect(settings.workingDirectory).to.equal(process.cwd()); 32 | }); 33 | it('Should accept new file filter', function() { 34 | var options = { 35 | referencingFileFilter: ['index.js'] 36 | }; 37 | config.set(options); 38 | var settings = config.get(); 39 | expect(settings.referencingFileFilter).to.have.length(1); 40 | expect(settings.referencingFileFilter).to.contain('index.js'); 41 | }); 42 | it('Should change modes and accept new file filter', function() { 43 | config.setMode('html'); 44 | var options = { 45 | referencingFileFilter: ['index.html'] 46 | }; 47 | config.set(options); 48 | var settings = config.get(); 49 | expect(settings.referencingFileFilter).to.have.length(1); 50 | expect(settings.referencingFileFilter).to.contain('index.html'); 51 | }); 52 | 53 | }); 54 | 55 | describe('Should accept custom rc file', function() { 56 | 57 | beforeEach(function() { 58 | config.reset(); 59 | var pathOfRcFile = path.join(process.cwd(), 'test/files/.reffixrc'); 60 | config.set(null, pathOfRcFile); 61 | }); 62 | afterEach(function() { 63 | config.reset(); 64 | }); 65 | 66 | it('Should add test mode', function() { 67 | config.setMode('test'); 68 | var settings = config.get(); 69 | expect(settings.referencingFileFilter).to.have.length(1); 70 | expect(settings.referencingFileFilter).to.contain('test.*'); 71 | expect(settings.referencedFileFilter).to.have.length(1); 72 | expect(settings.referencedFileFilter).to.contain('test_test.*'); 73 | expect(settings.workingDirectory).to.equal(process.cwd()); 74 | }); 75 | it('Should not change default settings', function() { 76 | config.setMode('default'); 77 | var settings = config.get(); 78 | expect(settings.referencingFileFilter).to.have.length(2); 79 | expect(settings.referencingFileFilter).to.contain('*.js'); 80 | expect(settings.referencingFileFilter).to.contain('*.jsx'); 81 | expect(settings.workingDirectory).to.equal(process.cwd()); 82 | }); 83 | 84 | }) 85 | 86 | }); -------------------------------------------------------------------------------- /test/FilteringSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var config = require('../lib/config'); 5 | var parser = require('../lib/parser'); 6 | 7 | describe('Filtering', function() { 8 | 9 | var testWorkingDir = 'test/files/filtering'; 10 | 11 | describe('Should filter directories', function() { 12 | var files; 13 | var options = { 14 | workingDirectory: testWorkingDir, 15 | "directoriesToExclude": [ 16 | ".*", 17 | "node_modules" 18 | ], 19 | "referencingFileFilter": [ 20 | "*.js", 21 | "*.jsx" 22 | ], 23 | "referencedFileFilter": [ 24 | "*.js", 25 | "*.jsx" 26 | ] 27 | }; 28 | beforeEach(function(done) { 29 | config.forceSet(options); 30 | parser.getReferences().then(function(result) { 31 | files = result; 32 | done(); 33 | }); 34 | }); 35 | afterEach(function() { 36 | config.reset(); 37 | }); 38 | it('Should get correct number of existing files by filtering directories', function() { 39 | expect(Object.keys(files.existingFiles)).to.have.length(5); 40 | }); 41 | it('Should get correct number of referencing files', function() { 42 | expect(Object.keys(files.referencingFiles)).to.have.length(4); 43 | }); 44 | }); 45 | 46 | describe('Should filter files by name', function() { 47 | var files; 48 | var options = { 49 | workingDirectory: testWorkingDir, 50 | "directoriesToExclude": [ 51 | ], 52 | "referencingFileFilter": [ 53 | "*1.*" 54 | ], 55 | "referencedFileFilter": [ 56 | "*.*", 57 | "!foo_file*.*" 58 | ] 59 | }; 60 | beforeEach(function(done) { 61 | config.forceSet(options); 62 | parser.getReferences().then(function(result) { 63 | files = result; 64 | done(); 65 | }); 66 | }); 67 | afterEach(function() { 68 | config.reset(); 69 | }); 70 | it('Should get correct number of referencing/referenced files', function() { 71 | expect(Object.keys(files.referencingFiles)).to.have.length(3); 72 | expect(Object.keys(files.referencedFiles)).to.have.length(2); 73 | expect(Object.keys(files.referencedFiles)).to.not.contain(path.join(process.cwd(), 'test/files/filtering/foo_file1.jsx')); 74 | }); 75 | }); 76 | 77 | describe('Should filter files by excluding sources', function() { 78 | var files; 79 | var options = { 80 | workingDirectory: testWorkingDir, 81 | "directoriesToExclude": [ 82 | ], 83 | "referencingFileFilter": [ 84 | "!foo*.*" 85 | ], 86 | "referencedFileFilter": [ 87 | "*.js", 88 | "*.jsx" 89 | ] 90 | }; 91 | beforeEach(function(done) { 92 | config.forceSet(options); 93 | parser.getReferences().then(function(result) { 94 | files = result; 95 | done(); 96 | }); 97 | }); 98 | afterEach(function() { 99 | config.reset(); 100 | }); 101 | it('Should get correct number of referencing/referenced files', function() { 102 | expect(Object.keys(files.referencingFiles)).to.have.length(2); 103 | expect(Object.keys(files.referencedFiles)).to.have.length(3); 104 | }); 105 | }); 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /test/OriginalModuleFilesSpec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('chai').expect; 3 | 4 | var config = require('../lib/config'); 5 | var parser = require('../lib/parser'); 6 | 7 | describe('Original Files', function() { 8 | 9 | var testWorkingDir = 'test/files/modules/original'; 10 | 11 | describe('References', function() { 12 | var files; 13 | var options = { 14 | workingDirectory: testWorkingDir 15 | }; 16 | beforeEach(function(done) { 17 | config.reset(); 18 | config.set(options); 19 | parser.getReferences().then(function(result) { 20 | files = result; 21 | done(); 22 | }); 23 | }); 24 | afterEach(function() { 25 | config.reset(); 26 | }); 27 | it('Should get correct number of files', function() { 28 | expect(Object.keys(files.existingFiles)).to.have.length(23); 29 | }); 30 | it('Should get correct number of referencing files', function() { 31 | expect(Object.keys(files.referencingFiles)).to.have.length(2); 32 | }); 33 | it('Should get correct file paths', function() { 34 | expect(files.existingFiles).to.have.property(path.join(process.cwd(), options.workingDirectory, 'file1.json')); 35 | expect(files.existingFiles).to.have.property(path.join(process.cwd(), options.workingDirectory, 'level1a/file-with-references.jsx')); 36 | expect(files.existingFiles).to.have.property(path.join(process.cwd(), options.workingDirectory, 'level1a/level2a/level3a/file8.js')); 37 | }); 38 | it('Should get correct number of references within files', function() { 39 | expect(Object.keys(files.referencedFiles)).to.have.length(12); 40 | }) 41 | }); 42 | 43 | describe('Filtered References with one glob', function() { 44 | var files; 45 | var options = { 46 | workingDirectory: testWorkingDir, 47 | referencingFileFilter: ['*.js'], 48 | referencedFileFilter: ['*.js'] 49 | }; 50 | beforeEach(function(done) { 51 | config.forceSet(options); 52 | parser.getReferences().then(function(result) { 53 | files = result; 54 | done(); 55 | }) 56 | }); 57 | it('Should get correct number of existing files', function() { 58 | expect(Object.keys(files.existingFiles)).to.have.length(12); 59 | }); 60 | it('Should get correct number of references within files', function() { 61 | expect(Object.keys(files.referencedFiles)).to.have.length(9); 62 | }) 63 | }); 64 | 65 | describe('Filtered References with two globs', function() { 66 | var files; 67 | var options = { 68 | workingDirectory: testWorkingDir, 69 | referencingFileFilter: ['*.js', '*.jsx'], 70 | referencedFileFilter: ['*.js', '*.jsx'] 71 | }; 72 | beforeEach(function(done) { 73 | config.forceSet(options); 74 | parser.getReferences().then(function(result) { 75 | files = result; 76 | done(); 77 | }) 78 | }); 79 | it('Should get correct number of existing files', function() { 80 | expect(Object.keys(files.existingFiles)).to.have.length(15); 81 | }); 82 | it('Should get correct number of references within files', function() { 83 | expect(Object.keys(files.referencedFiles)).to.have.length(12); 84 | }) 85 | }); 86 | 87 | describe('Broken References', function() { 88 | var brokenReferences; 89 | var options = { 90 | workingDirectory: testWorkingDir 91 | }; 92 | beforeEach(function(done) { 93 | config.forceSet(options); 94 | parser.getBrokenReferences().then(function(result) { 95 | brokenReferences = result.brokenReferences; 96 | done(); 97 | }) 98 | }); 99 | it('Should find no broken references', function() { 100 | expect(Object.keys(brokenReferences)).to.have.length(0); 101 | }); 102 | }); 103 | 104 | }); -------------------------------------------------------------------------------- /lib/gitParser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var execa = require('execa'); 4 | var path = require('path'); 5 | // parser must be loaded at runtime because it calls gitParser, 6 | // which then calls parser; must ensure that parser has finished loading 7 | // before it is called by gitParser 8 | var parser; 9 | 10 | var utils = require('./utils'); 11 | var config = require('./config'); 12 | 13 | var gitParser = { 14 | 15 | renamedFiles: {}, 16 | 17 | parse: function() { 18 | var self = this; 19 | this.loadParser(); 20 | return this.getStatus().then(function(statusData) { 21 | if (statusData) { 22 | self.renamedFiles = self.parseStatus(statusData); 23 | return true; 24 | } 25 | return false; 26 | }); 27 | }, 28 | 29 | getCorrectedPathFromGit: function(brokenReference) { 30 | // If referencing file was moved 31 | var originalPathBeforeReferencingFileWasMoved = this.getCorrectedPathIfReferencingFileWasMoved(brokenReference); 32 | var pathToCheckToSeeIfReferencedFileWasMoved = originalPathBeforeReferencingFileWasMoved || brokenReference.referencedFile; 33 | // If referenced file was moved 34 | var originalPathBeforeReferencedFileWasMoved = this.getCorrectedPathIfReferencedFileWasMoved(pathToCheckToSeeIfReferencedFileWasMoved); 35 | var correctedPath = originalPathBeforeReferencedFileWasMoved || originalPathBeforeReferencingFileWasMoved || null; 36 | return correctedPath; 37 | }, 38 | 39 | loadParser: function() { 40 | parser = require('./parser'); 41 | }, 42 | 43 | getStatus: function() { 44 | var workingDirectory = config.get().workingDirectory; 45 | var options = [ 46 | '--git-dir=' + path.join(workingDirectory, '/.git'), 47 | '--work-tree=' + workingDirectory, 48 | 'status', 49 | '--porcelain' 50 | ]; 51 | return execa('git', options) 52 | .then(function(result) { 53 | return result.stdout; 54 | }) 55 | .catch(function() { 56 | return null; 57 | }); 58 | }, 59 | 60 | parseStatus: function(status) { 61 | var renamedFiles = {}; 62 | var workingDirectory = config.get().workingDirectory; 63 | var modifiedFilePattern = /(?:^|\n)R[ A-Z] (.+?) -> ([^\n]+)/g; 64 | var match; 65 | while ((match = modifiedFilePattern.exec(status)) !== null) { 66 | var oldPath = path.resolve(workingDirectory, match[1].replace(/['"]/g, '')); 67 | var newPath = path.resolve(workingDirectory, match[2].replace(/['"]/g, '')); 68 | renamedFiles[oldPath] = newPath; 69 | } 70 | return renamedFiles; 71 | }, 72 | 73 | getCorrectedPathIfReferencingFileWasMoved: function(brokenReference) { 74 | var pathOfFirstReferencingFile = brokenReference.referencingFiles[0]; 75 | var originalPathOfReferencingFile = this.getOriginalPath(pathOfFirstReferencingFile); 76 | if (originalPathOfReferencingFile) { 77 | var referencedPath = brokenReference.referencedFile; 78 | var relativePathOfBrokenReference = parser.getRelativePathOfReference(pathOfFirstReferencingFile, referencedPath); 79 | var referencedPathBeforeFileWasMoved = utils.getAbsolutePathFromRelativePath(originalPathOfReferencingFile, relativePathOfBrokenReference); 80 | return referencedPathBeforeFileWasMoved; 81 | } 82 | return null; 83 | }, 84 | 85 | getCorrectedPathIfReferencedFileWasMoved: function(referencedPath) { 86 | var pathOfRenamedFile = this.renamedFiles[referencedPath]; 87 | return pathOfRenamedFile || null; 88 | }, 89 | 90 | getOriginalPath: function(renamedPathToFind) { 91 | for (var originalPath in this.renamedFiles) { 92 | var renamedPath = this.renamedFiles[originalPath]; 93 | if (renamedPath === renamedPathToFind) { 94 | return originalPath; 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | }; 101 | 102 | module.exports = { 103 | parse: gitParser.parse.bind(gitParser), 104 | getCorrectedPathFromGit: gitParser.getCorrectedPathFromGit.bind(gitParser) 105 | }; -------------------------------------------------------------------------------- /lib/prompter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inquirer = require('inquirer'); 4 | var clc = require('cli-color'); 5 | var Q = require('q'); 6 | 7 | var config = require('./config').default; 8 | var parser = require('./parser'); 9 | var updater = require('./updater'); 10 | var reporter = require('./reporter'); 11 | 12 | var prompter = { 13 | 14 | start: function(options) { 15 | var self = this; 16 | config.set(options); 17 | var settings = config.get(); 18 | console.log('\n' + clc.blue.bold('Looking for broken references in ') + clc.blue(settings.workingDirectory)); 19 | parser.getAll() 20 | .then(function(references) { 21 | console.log(clc.blue.bold('Searching ' + Object.keys(references.existingFiles).length + ' files\n')); 22 | console.log(parser.getReport(references.brokenReferences)); 23 | self.promptToCorrect(references.brokenReferences); 24 | }) 25 | .handleError(); 26 | }, 27 | 28 | promptToCorrect: function(brokenReferences) { 29 | var self = this; 30 | var numberOfFilesToFix = parser.getNumberOfFilesToFix(brokenReferences); 31 | var referencesWithMultiplePossibleCorrections = parser.getReferencesWithMultiplePossibleCorrections(brokenReferences); 32 | if (numberOfFilesToFix || Object.keys(referencesWithMultiplePossibleCorrections).length) { 33 | Q(inquirer.prompt({ 34 | type: 'confirm', 35 | name: 'correct', 36 | message: 'Update files to correct references?' 37 | })) 38 | .then(function(confirmed) { 39 | if (confirmed.correct) { 40 | return self.correctAndReport(brokenReferences); 41 | } 42 | return false; 43 | }) 44 | .then(function(promptToFix) { 45 | if (promptToFix) { 46 | self.promptToSelectCorrectPath(brokenReferences); 47 | } 48 | }) 49 | .handleError(); 50 | } 51 | 52 | }, 53 | 54 | // PRIVATE METHODS 55 | 56 | correctAndReport: function(brokenReferences) { 57 | return updater.update(brokenReferences) 58 | .then(function(namesArrayOfFilesFixed) { 59 | reporter.showReport(reporter.reportFilesUpdated(namesArrayOfFilesFixed)); 60 | return true; 61 | }) 62 | .handleError(); 63 | }, 64 | 65 | promptToSelectCorrectPath: function(brokenReferences) { 66 | var self = this; 67 | var referencesWithMultiplePossibleCorrections = parser.getReferencesWithMultiplePossibleCorrections(brokenReferences); 68 | if (!referencesWithMultiplePossibleCorrections.length) { 69 | return; 70 | } 71 | var questions = []; 72 | referencesWithMultiplePossibleCorrections.forEach(function(referenceWithMultipleOptions) { 73 | var newQuestions = self.setPromptQuestionForEachFile(referenceWithMultipleOptions); 74 | questions = questions.concat(newQuestions); 75 | }); 76 | console.log('\n'); 77 | Q(inquirer.prompt(questions)) 78 | .then(function(answers) { 79 | var fixedReferences = []; 80 | for (var referenceToCorrect in answers) { 81 | var brokenReference = brokenReferences.find(function(reference) { 82 | return reference.referencedFile === referenceToCorrect; 83 | }); 84 | brokenReference.correctPath = answers[referenceToCorrect]; 85 | fixedReferences.push(brokenReference); 86 | } 87 | self.correctAndReport(fixedReferences); 88 | }) 89 | .handleError(); 90 | }, 91 | 92 | setPromptQuestionForEachFile: function(referenceWithMultipleOptions) { 93 | var questions = []; 94 | referenceWithMultipleOptions.referencingFiles.forEach(function(referencingFile) { 95 | questions.push({ 96 | type: 'list', 97 | name: referenceWithMultipleOptions.referencedFile, 98 | message: clc.blue('\nChoose the correct path for this broken reference: ') + referenceWithMultipleOptions.referencedFile + clc.blue('\nwhich is contained in this file: ') + referencingFile + clc.blue('\nSelect one of the following paths:\n'), 99 | choices: referenceWithMultipleOptions.possibleCorrectPaths 100 | }); 101 | }); 102 | return questions; 103 | } 104 | 105 | }; 106 | 107 | module.exports = { 108 | start: prompter.start.bind(prompter), 109 | promptToCorrect: prompter.promptToCorrect.bind(prompter) 110 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mime = require('mime'); 6 | var Q = require('q'); 7 | var minimatch = require('minimatch'); 8 | var XRegExp = require('../vendor/xregexp-all'); 9 | 10 | var utils = { 11 | 12 | getSearchPatterns: function(replacementString, options) { 13 | var valuePattern = replacementString 14 | ? XRegExp.escape(replacementString) 15 | : options.valuePattern; 16 | return options.searchPatterns.map(function(generalPattern) { 17 | return XRegExp.build(generalPattern, { 18 | valuePattern: XRegExp('(?' + valuePattern + ')') 19 | }, 'g'); 20 | }); 21 | }, 22 | 23 | getAbsolutePathFromRelativePath: function(pathOfFileContainingReference, relativePathOfReference) { 24 | var dirOfFileContainingReference = path.dirname(pathOfFileContainingReference); 25 | var absolutePathOfReference = path.resolve(dirOfFileContainingReference, relativePathOfReference); 26 | var absolutePathOfReferenceWithFileExtension = this.restoreJsFileExtension(absolutePathOfReference); 27 | return absolutePathOfReferenceWithFileExtension; 28 | }, 29 | 30 | getRelativePathFromAbsolutePath: function(pathOfReferencingFile, absolutePathOfReference, currentDirectoryPrefix) { 31 | var relativeDir = path.relative(path.dirname(pathOfReferencingFile), path.dirname(absolutePathOfReference)); 32 | var relativePath = path.join(relativeDir, path.basename(absolutePathOfReference)); 33 | if (relativePath.substring(0, 1) !== '.') { 34 | relativePath = currentDirectoryPrefix + relativePath; 35 | } 36 | return relativePath; 37 | }, 38 | 39 | // TODO Should add found files to existingFiles and return existingFiles 40 | doesFileExist: function(filePath, existingFiles) { 41 | if (!filePath) { 42 | return Q(false); 43 | } 44 | if (existingFiles.hasOwnProperty(filePath)) { 45 | return Q(true); 46 | } 47 | return Q.nfcall(fs.stat, filePath) 48 | .then(function() { 49 | return true; 50 | }) 51 | .catch(function() { 52 | return false; 53 | }); 54 | }, 55 | 56 | // TODO Pass in presorted glob arrays to improve performance 57 | isFileIncluded: function(filePath, globArray) { 58 | var inclusionGlobs = this.filterGlobs(globArray, 'inclusion'); 59 | var exclusionGlobs = this.filterGlobs(globArray, 'exlusion'); 60 | // One of the inclusion conditions must be met, unless array is empty 61 | var isInclusionCriteriaMet = false; 62 | if (inclusionGlobs.length === 0) { 63 | isInclusionCriteriaMet = true; 64 | } else { 65 | isInclusionCriteriaMet = inclusionGlobs.some(function(pattern) { 66 | return minimatch(filePath, pattern, {matchBase: true}); 67 | }); 68 | } 69 | if (!isInclusionCriteriaMet) { 70 | return false; 71 | } 72 | if (exclusionGlobs.length === 0) { 73 | return true; 74 | } 75 | // All of the exlcusion conditions must be met 76 | return exclusionGlobs.every(function(pattern) { 77 | return minimatch(filePath, pattern, {matchBase: true}); 78 | }); 79 | }, 80 | 81 | filterGlobs: function(globs, type) { 82 | var types = { 83 | inclusion: true, 84 | exlusion: false 85 | }; 86 | var filteredGlobs = []; 87 | globs.forEach(function(glob) { 88 | if ((glob.indexOf('!') !== 0) === types[type]) { 89 | filteredGlobs.push(glob); 90 | } 91 | }); 92 | return filteredGlobs; 93 | }, 94 | 95 | restoreJsFileExtension: function(filePath) { 96 | mime.default_type = 'none'; 97 | if (path.extname(filePath) === '') { 98 | filePath += '.js'; 99 | } else { 100 | // Check if .* is a valid extension or part of the filename 101 | var mimeType = mime.lookup(filePath); 102 | if (mimeType === 'none') { 103 | filePath += '.js'; 104 | } 105 | } 106 | return filePath; 107 | }, 108 | 109 | toCamelCase: function(str) { 110 | return str.replace(/-(\w)/g, function(match, group1) { 111 | return group1.toUpperCase(); 112 | }); 113 | }, 114 | 115 | stringToArray: function(str) { 116 | if (str) { 117 | return str.split(/, *| +/); 118 | } 119 | return null; 120 | } 121 | 122 | }; 123 | 124 | module.exports = utils; -------------------------------------------------------------------------------- /lib/updater.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var readdirp = require('readdirp'); 6 | var Q = require('q'); 7 | var qe = require('./qExtensions'); 8 | var XRegExp = require('../vendor/xregexp-all'); 9 | 10 | var utils = require('./utils'); 11 | var config = require('./config'); 12 | var parser = require('./parser'); 13 | 14 | var updater = { 15 | 16 | fixReferences: function() { 17 | var self = this; 18 | return parser.getBrokenReferences() 19 | .then(function(result) { 20 | return self.update(result.brokenReferences); 21 | }) 22 | .handleError(); 23 | }, 24 | 25 | update: function(brokenReferences) { 26 | var self = this; 27 | var filesToFix = self.getFilesToFix(brokenReferences); 28 | return self.correctReferences(filesToFix) 29 | .then(function(namesArrayOfFilesFixed) { 30 | return namesArrayOfFilesFixed; 31 | }) 32 | .handleError(); 33 | }, 34 | 35 | getFilesToFix: function(brokenReferences) { 36 | var filesToFix = {}; 37 | brokenReferences.forEach(function(brokenReference) { 38 | if (!brokenReference.correctPath) { 39 | return; 40 | } 41 | // TODO Make this a subroutine called getRelativePathsForFilesToFix 42 | brokenReference.referencingFiles.forEach(function(pathOfReferencingFile) { 43 | var relativePathImported = parser.getRelativePathOfReference(pathOfReferencingFile, brokenReference.referencedFile); 44 | var correctedRelativePath = utils.getRelativePathFromAbsolutePath(pathOfReferencingFile, brokenReference.correctPath, config.get().currentDirectoryPrefix); 45 | if (!filesToFix[pathOfReferencingFile]) { 46 | filesToFix[pathOfReferencingFile] = {referencesToFix: []}; 47 | } 48 | filesToFix[pathOfReferencingFile].referencesToFix.push({ 49 | relativePathImported: relativePathImported, 50 | correctedRelativePath: correctedRelativePath 51 | }); 52 | }); 53 | }); 54 | return filesToFix; 55 | }, 56 | 57 | correctReferences: function(filesToFix) { 58 | var self = this; 59 | var promises = []; 60 | if (Object.keys(filesToFix).length) { 61 | Object.keys(filesToFix).forEach(function(pathOfFileToFix) { 62 | var deferred = Q.defer(); 63 | promises.push(deferred.promise); 64 | // TODO Use a promise here 65 | fs.readFile(pathOfFileToFix, 'utf8', function(err, fileContent) { 66 | if (!err) { 67 | var referencesToFix = filesToFix[pathOfFileToFix].referencesToFix; 68 | var correctedFileContent = fileContent; 69 | referencesToFix.forEach(function(referenceToFix) { 70 | correctedFileContent = self.replaceImportStringInFile(correctedFileContent, referenceToFix.relativePathImported, referenceToFix.correctedRelativePath); 71 | }); 72 | fs.writeFile(pathOfFileToFix, correctedFileContent, 'utf8', function(err) { 73 | if (!err) { 74 | deferred.resolve(pathOfFileToFix); 75 | } else { 76 | deferred.reject('Error writing updated file: ' + pathOfFileToFix + ' -- ' + err.message); 77 | console.error('Error writing updated file ', err); 78 | } 79 | }); 80 | } else { 81 | deferred.reject('Couldn\'t read file: ' + pathOfFileToFix + ' -- ' + err.message); 82 | } 83 | }); 84 | }); 85 | return Q.allSettled(promises) 86 | .then(function(results) { 87 | var namesArrayOfFixedFiles = results.map(function(result) { 88 | return result.value || result.reason; 89 | }); 90 | return namesArrayOfFixedFiles; 91 | }) 92 | .handleError(); 93 | } else { 94 | return Q([]); 95 | } 96 | }, 97 | 98 | // PRIVATE METHODS 99 | 100 | replaceImportStringInFile: function(fileContent, searchString, replacementString) { 101 | var searchPatterns = utils.getSearchPatterns(searchString, config.get()); 102 | var correctedFileContent = fileContent; 103 | searchPatterns.forEach(function(searchPattern) { 104 | correctedFileContent = correctedFileContent.replace(searchPattern, function(match) { 105 | return match.replace(searchString, replacementString); 106 | }); 107 | }); 108 | return correctedFileContent; 109 | } 110 | 111 | }; 112 | 113 | module.exports = { 114 | fixReferences: updater.fixReferences.bind(updater), 115 | update: updater.update.bind(updater), 116 | getFilesToFix: updater.getFilesToFix.bind(updater), 117 | correctReferences: updater. correctReferences.bind(updater) 118 | }; -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var deepcopy = require('deepcopy'); 6 | 7 | var baseSettings = require('../config.json'); 8 | var rc = require('rc')('reffix'); 9 | var reporter = require('./reporter'); 10 | 11 | var config = { 12 | 13 | settings: null, 14 | mode: null, 15 | 16 | get: function() { 17 | var settings = deepcopy(this.settings); 18 | return settings[this.mode]; 19 | }, 20 | 21 | // TODO Make this into subroutines 22 | set: function(optionsFromCommandLine, pathOfCustomConfigFile) { 23 | if (pathOfCustomConfigFile) { 24 | this.addSettingsGroup(this.settings, pathOfCustomConfigFile); 25 | } 26 | if (optionsFromCommandLine) { 27 | if (optionsFromCommandLine.mode) { 28 | this.setMode(optionsFromCommandLine.mode); 29 | } 30 | var settings = this.get(); 31 | if (optionsFromCommandLine.workingDirectory) { 32 | settings.workingDirectory = path.resolve(process.cwd(), optionsFromCommandLine.workingDirectory); 33 | } 34 | if (optionsFromCommandLine.directoriesToExclude) { 35 | // TODO Avoid adding duplicates to array 36 | settings.directoriesToExclude = settings.directoriesToExclude.concat(optionsFromCommandLine.directoriesToExclude); 37 | } 38 | if (optionsFromCommandLine.referencingFileFilter && optionsFromCommandLine.referencingFileFilter.length) { 39 | settings.referencingFileFilter = optionsFromCommandLine.referencingFileFilter.slice(); 40 | } 41 | if (optionsFromCommandLine.referencedFileFilter && optionsFromCommandLine.referencedFileFilter.length) { 42 | settings.referencedFileFilter = optionsFromCommandLine.referencedFileFilter.slice(); 43 | } 44 | this.settings[this.mode] = settings; 45 | } 46 | }, 47 | 48 | // TODO Better name for this; or arg that specifies whether to overwrite directory exclusions 49 | forceSet: function(options) { 50 | if (options) { 51 | this.settings[this.mode] = Object.assign(this.settings[this.mode], options); 52 | } 53 | }, 54 | 55 | getMode: function() { 56 | return this.mode; 57 | }, 58 | 59 | setMode: function(mode) { 60 | if (!this.isModeValid(mode)) { 61 | reporter.error('Illegal mode value: ' + mode); 62 | process.exit(); 63 | } else { 64 | this.mode = mode; 65 | } 66 | }, 67 | 68 | reset: function() { 69 | this.init(); 70 | }, 71 | 72 | // PRIVATE METHODS 73 | 74 | init: function() { 75 | this.mode = 'default'; 76 | var pathOfRcFile = rc.config; 77 | this.addSettingsGroup(baseSettings, pathOfRcFile); 78 | }, 79 | 80 | addSettingsGroup: function(settingsGroupToAdd, pathOfRcFile) { 81 | var settingsGroup = deepcopy(settingsGroupToAdd); 82 | settingsGroup = this.mergeRc(settingsGroup, pathOfRcFile); 83 | settingsGroup = this.replaceVariables(settingsGroup); 84 | settingsGroup = this.mergeModesWithDefault(settingsGroup); 85 | this.settings = settingsGroup; 86 | }, 87 | 88 | mergeModesWithDefault: function(settings) { 89 | var newSettings = deepcopy(settings); 90 | for (var mode in newSettings) { 91 | if (mode !== 'default') { 92 | newSettings[mode] = Object.assign({}, deepcopy(newSettings.default), newSettings[mode]); 93 | } 94 | } 95 | return newSettings; 96 | }, 97 | 98 | mergeRc: function(allSettings, pathOfRcFile) { 99 | // TODO should have error message 100 | var customSettings = pathOfRcFile 101 | ? JSON.parse(fs.readFileSync(pathOfRcFile, 'utf8')) 102 | : null; 103 | if (!customSettings) { 104 | return allSettings; 105 | } 106 | var newSettings = deepcopy(allSettings); 107 | for (var mode in customSettings) { 108 | newSettings[mode] = Object.assign({}, newSettings[mode], deepcopy(customSettings[mode])); 109 | } 110 | return newSettings; 111 | }, 112 | 113 | replaceVariables: function(settings) { 114 | for (var mode in settings) { 115 | var settingsForMode = settings[mode]; 116 | // Set '.' or './' to cwd 117 | if (/\.\/?/.test(settingsForMode.workingDirectory)) { 118 | settingsForMode.workingDirectory = process.cwd(); 119 | } 120 | } 121 | return settings; 122 | }, 123 | 124 | isModeValid: function(mode) { 125 | var validModes = Object.keys(this.settings); 126 | return validModes.indexOf(mode) !== -1; 127 | } 128 | 129 | }; 130 | 131 | config.init(); 132 | 133 | module.exports = { 134 | get: config.get.bind(config), 135 | set: config.set.bind(config), 136 | forceSet: config.forceSet.bind(config), 137 | getMode: config.getMode.bind(config), 138 | setMode: config.setMode.bind(config), 139 | reset: config.reset.bind(config) 140 | }; -------------------------------------------------------------------------------- /lib/reporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var clc = require('cli-color'); 4 | 5 | var reporter = { 6 | 7 | indent: ' ', 8 | 9 | showReport: function(report) { 10 | if (report) { 11 | console.log(report + '\n'); 12 | } 13 | }, 14 | 15 | reportFilesChecked: function(fileData) { 16 | var message = ''; 17 | var numberOfFilesChecked = Object.keys(fileData.existingFiles).length; 18 | if (numberOfFilesChecked > 1) { 19 | message = numberOfFilesChecked + ' files parsed'; 20 | } else if (numberOfFilesChecked === 1) { 21 | message = '1 file parsed'; 22 | } else { 23 | message = 'No files to parse' 24 | } 25 | return message; 26 | }, 27 | 28 | getReport: function(fileData, options) { 29 | var self = this; 30 | var messages = []; 31 | if (options.referencingFiles) { 32 | messages = messages.concat(self.reportReferences(fileData, 'referencingFiles')); 33 | } 34 | if (options.referencedFiles) { 35 | messages = messages.concat(self.reportReferences(fileData, 'referencedFiles')); 36 | } 37 | if (options.brokenReferences) { 38 | messages = messages.concat(self.reportBrokenReferences(fileData)); 39 | } 40 | return messages.join('\n'); 41 | }, 42 | 43 | reportFilesUpdated: function(messages) { 44 | if (messages.length) { 45 | messages.unshift(clc.green.bold('\nFiles updated to correct references:')); 46 | return messages.join('\n') + '\n'; 47 | } 48 | return ''; 49 | }, 50 | 51 | error: function(message) { 52 | console.error(clc.red('\n' + message + '\n')); 53 | }, 54 | 55 | // PRIVATE METHODS 56 | 57 | reportReferences: function(fileData, fileType) { 58 | var self = this; 59 | var headingsByFileType = { 60 | referencingFiles: { 61 | countSingular: '1 file contains references', 62 | countPlural: ' files contain references', 63 | mainFile: 'File Containing Reference(s)', 64 | subFiles: 'Files Referenced', 65 | noFiles: 'No files contain references' 66 | }, 67 | referencedFiles: { 68 | countSingular: '1 file is referenced by other files', 69 | countPlural: ' files are referenced by other files', 70 | mainFile: 'File Referenced', 71 | subFiles: 'Files Containing Reference', 72 | noFiles: 'No files are referenced by other files' 73 | } 74 | }; 75 | var headings = headingsByFileType[fileType]; 76 | var filesToReport = fileData[fileType]; 77 | var numberOfFilesToReport = Object.keys(filesToReport).length; 78 | var messages = ['']; 79 | if (numberOfFilesToReport) { 80 | var message = (numberOfFilesToReport === 1) ? headings.countSingular +'\n' : numberOfFilesToReport + headings.countPlural + '\n'; 81 | messages.push(clc.bold(message)); 82 | for (var pathToReport in filesToReport) { 83 | messages.push(clc.blue.bold(headings.mainFile + ': ') + clc.blue(pathToReport)); 84 | messages.push(clc.bold(self.indent + headings.subFiles)); 85 | var subFiles = filesToReport[pathToReport]; 86 | messages = messages.concat(self.formatFileList(subFiles, fileType)); 87 | messages.push(''); 88 | } 89 | } else { 90 | messages.push(clc.bold(headings.noFiles)); 91 | } 92 | messages.push(''); 93 | 94 | return messages; 95 | 96 | }, 97 | 98 | reportBrokenReferences: function(fileData) { 99 | var brokenReferences = fileData.brokenReferences; 100 | var messages = []; 101 | if (brokenReferences.length) { 102 | brokenReferences.forEach(function(brokenReference) { 103 | messages.push(clc.red.bold('Broken Reference: ') + clc.red(brokenReference.referencedFile)); 104 | if (brokenReference.correctPath) { 105 | messages.push(clc.green.bold('Corrected Reference: ') + clc.green(brokenReference.correctPath)); 106 | } else if (brokenReference.possibleCorrectPaths.length > 1) { 107 | messages.push(clc.red.bold('Couldn\'t determine correct reference from among these possibilities:')); 108 | messages.push(clc.red(brokenReference.possibleCorrectPaths.join('\n'))); 109 | } else { 110 | messages.push(clc.green.bold('Corrected Reference: ') + clc.red('Couldn\'t determine correct reference')); 111 | } 112 | messages.push(clc.bold('Files Containing Broken Reference:')); 113 | brokenReference.referencingFiles.forEach(function(referencingFilePath) { 114 | messages.push(' ' + referencingFilePath); 115 | }); 116 | messages.push(''); 117 | }); 118 | } else { 119 | messages.push(clc.green.bold('No broken references found\n')); 120 | } 121 | return messages; 122 | }, 123 | 124 | formatFileList: function(fileList, fileType) { 125 | var self = this; 126 | var messages = []; 127 | if (fileType === 'referencingFiles') { 128 | fileList.forEach(function(file) { 129 | messages.push(self.indent + file.absoluteReference); 130 | }); 131 | } else { 132 | fileList.referencingFiles.forEach(function(filePath) { 133 | messages.push(self.indent + filePath); 134 | }); 135 | } 136 | return messages; 137 | } 138 | 139 | }; 140 | 141 | module.exports = { 142 | showReport: reporter.showReport.bind(reporter), 143 | reportFilesChecked: reporter.reportFilesChecked.bind(reporter), 144 | getReport: reporter.getReport.bind(reporter), 145 | reportFilesUpdated: reporter.reportFilesUpdated.bind(reporter), 146 | error: reporter.error.bind(reporter) 147 | }; -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var program = require('commander'); 4 | 5 | var utils = require('./utils'); 6 | var config = require('./config'); 7 | var parser = require('./parser'); 8 | var updater = require('./updater'); 9 | var reporter = require('./reporter'); 10 | var prompter = require('./prompter'); 11 | 12 | var cli = { 13 | 14 | start: function() { 15 | 16 | this.configureOptions(); 17 | var options = this.parseCommandLine(); 18 | this.execute(options.reportOptions, options.processingOptions); 19 | 20 | }, 21 | 22 | // PRIVATE METHODS 23 | 24 | configureOptions: function() { 25 | 26 | program 27 | .version('0.1') 28 | .description('Finds and repairs broken references in the working directory. Enclose all option values in quotes.') 29 | .usage('[options] [--] [working_directory]') 30 | 31 | // Configuration options 32 | .option('-c, --config ', 'path of custom configuration file') 33 | .option('-m, --mode ', 'type of references to look for, JS import/require (default) or HTML href/src (html)') 34 | .option('-f, --files [target_globs]', 'filter the source (referencing) and target (referenced) files parsed (e.g., \'*1.js,*2.js\' \'file.js\'') 35 | .option('-d, --dirs ', 'exclude from sources the specified directories (e.g., \'test,bin\')') 36 | 37 | // Report options 38 | .option('-e, --errors', 'list broken references and prompt to fix them') 39 | .option('-s, --sources', 'list files (sources of references) containing references to other files') 40 | .option('-t, --targets', 'list files (targets of references) referenced by other files') 41 | 42 | // Processing options 43 | .option('-n, --no-prompts', 'fix broken references without prompting (unless -r)') 44 | .option('-r, --report-only', 'don\'t fix broken references, just display reports') 45 | .parse(process.argv); 46 | 47 | }, 48 | 49 | parseCommandLine: function() { 50 | 51 | var options = {}; 52 | var cliResults = this.getMultipleArgsForOptions(); 53 | if (cliResults.mode) { 54 | options.mode = cliResults.mode[0]; 55 | } 56 | options.workingDirectory = cliResults._trailingArg; 57 | if (cliResults.dirs) { 58 | options.directoriesToExclude = utils.stringToArray(cliResults.dirs[0]); 59 | } 60 | if (cliResults.files) { 61 | options.referencingFileFilter = utils.stringToArray(cliResults.files[0]); 62 | options.referencedFileFilter = utils.stringToArray(cliResults.files[1]); 63 | } 64 | 65 | var pathOfCustomConfigFile = program.config || null; 66 | config.set(options, pathOfCustomConfigFile); 67 | 68 | var reportOptions = { 69 | brokenReferences: Boolean(program.errors), 70 | referencingFiles: Boolean(program.sources), 71 | referencedFiles: Boolean(program.targets) 72 | }; 73 | if (!reportOptions.brokenReferences && !reportOptions.referencingFiles && !reportOptions.referencedFiles) { 74 | reportOptions.brokenReferences = true; 75 | } 76 | 77 | var processingOptions = { 78 | fixBrokenReferences: !cliResults.reportOnly && reportOptions.brokenReferences, 79 | promptToFix: !cliResults.noPrompts 80 | }; 81 | 82 | return { 83 | reportOptions: reportOptions, 84 | processingOptions: processingOptions 85 | }; 86 | 87 | }, 88 | 89 | // Commander doesn't suppport multiple arguments for an options, 90 | // that support is added here 91 | getMultipleArgsForOptions: function() { 92 | 93 | var argsToParse = []; 94 | var appArgs = program.rawArgs.slice(2); 95 | appArgs.forEach(function(arg) { 96 | // Expand grouped short options (-abc) 97 | var argLength = arg.length; 98 | if (arg.indexOf('-') === 0 && argLength > 2) { 99 | for (var i = 1; i < argLength; i ++) { 100 | argsToParse.push('-' + arg[i]); 101 | } 102 | } else { 103 | argsToParse.push(arg); 104 | } 105 | }); 106 | 107 | var optionValues = {}; 108 | var optionName = ''; 109 | var numOfArgsRemaining = 0; 110 | argsToParse.some(function(arg, ind) { 111 | var matchingOption = program.options.find(function(option) { 112 | return arg === option.short || arg === option.long; 113 | }); 114 | if (matchingOption) { 115 | var hyphenatedOptionName = matchingOption.long.substr(2); 116 | optionName = utils.toCamelCase(hyphenatedOptionName); 117 | var matchArgs = matchingOption.flags.match(/\[.+?\]|<.+?>/g); 118 | numOfArgsRemaining = matchArgs ? matchArgs.length : 0; 119 | if (numOfArgsRemaining) { 120 | optionValues[optionName] = []; 121 | } else { 122 | optionValues[optionName] = true; 123 | } 124 | } else if (arg === '--') { 125 | var trailingArg = argsToParse[ind + 1]; 126 | if (trailingArg) { 127 | optionValues._trailingArg = trailingArg; 128 | } 129 | return true; 130 | } else if (numOfArgsRemaining) { 131 | // Some terminals retain the quotes around the args 132 | var argWithQuotesRemoved = arg.replace(/['"]/g, ''); 133 | optionValues[optionName].push(argWithQuotesRemoved); 134 | numOfArgsRemaining --; 135 | // Get optional trailing arg 136 | } else if (ind === argsToParse.length - 1) { 137 | optionValues._trailingArg = arg; 138 | } 139 | return false; 140 | }); 141 | return optionValues; 142 | 143 | }, 144 | 145 | execute: function(reportOptions, processingOptions) { 146 | 147 | parser.getAll() 148 | .then(function(fileData) { 149 | console.log(''); 150 | reporter.showReport(reporter.reportFilesChecked(fileData)); 151 | reporter.showReport(reporter.getReport(fileData, reportOptions)); 152 | return fileData; 153 | }) 154 | .then(function(fileData) { 155 | if (!processingOptions.fixBrokenReferences) { 156 | return false; 157 | } 158 | if (processingOptions.promptToFix) { 159 | prompter.promptToCorrect(fileData.brokenReferences); 160 | return false; 161 | } else { 162 | return updater.update(fileData.brokenReferences); 163 | } 164 | }) 165 | .then(function(namesArrayOfFilesFixed) { 166 | if (namesArrayOfFilesFixed) { 167 | reporter.showReport(reporter.reportFilesUpdated(namesArrayOfFilesFixed)); 168 | } 169 | }) 170 | .handleError(); 171 | 172 | } 173 | 174 | }; 175 | 176 | module.exports = { 177 | start: cli.start.bind(cli) 178 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # reffix 3 | 4 | **Reference Fixer** -- the easy fix for broken file path references. Analyze and batch-repair broken references across an entire project from the command line. 5 | 6 | ## Features 7 | 8 | - Fixes broken `require` and `import` statements in JavaScript files 9 | - Fixes broken `src` and `href` attributes in HTML files 10 | - Customizable to support other types of references 11 | - Expands references you type into your files 12 | - Generates detailed reports about all the file path dependencies in a project 13 | - Comprehensive filtering options 14 | - Extremely fast asynchronous performance 15 | 16 | ## Install 17 | 18 | `npm install --global reffix` 19 | 20 | ## Command Line Usage 21 | 22 | `$ ref` 23 | or 24 | `$ reffix` 25 | 26 | Fixing broken references couldn't be easier. By default, **reffix** will generate a report of broken module references (`require` and `import` statements) by recursing through all the files in the directory in which it is launched. Use the `-m html` option to parse HTML files instead. **reffix** will list the broken and corrected paths and prompt you to update the files to fix them. Just hit `Enter` to make the repairs. All reports list absolute paths, but relative paths will be used to update the files. 27 | 28 | ![Command line output](img/example1.png) 29 | 30 | If git version control is available for the files being parsed, **reffix** will use data from git status to fix references broken when files were renamed or moved. If git is unavailable, the program can repair references broken as a result of files being moved but not as a result of files being renamed. If a broken reference refers to a filename that is shared by mutliple files in different locations, reffix may not be able to determine which path to use and will prompt you to select the correct one. 31 | 32 | ![Prompt to choose file](img/example2.png) 33 | 34 | Using the command-line options described below, you can generate reports showing which files contain references and which files they refer to. You can filter the parsed files via command line options or configuration settings in a `.reffixrc` configuration file. Edit the configuration file to add support for additional types of file path references. 35 | 36 | ## Path Expansion 37 | 38 | When you type a reference into your code but don't know offhand the full path of the relevant file, use **reffix** to automatically expand the filename into a full-path reference. 39 | 40 | Type this into your code: 41 | `'./myFile.js'` 42 | Run **reffix** and you'll see this when you reload the file: 43 | `'../../full/path/of/myFile.js'`. 44 | 45 | ## Command Line Options 46 | 47 | ### General Usage 48 | 49 | `ref [options] [--] [working_directory]` 50 | 51 | Each option has a short (`-o`) and a long (`--option`) version that you can use interchangeably. Short options can be combined into a single argument (for example, `-ste`). `working_directory` is the starting path to use for parsing files. It defaults to the directory from which the command is executed. If `working_directory` is preceded by option arguments, it must be immediately preceded by `--`. 52 | 53 | ### Error Correction 54 | 55 | - `-e --errors` - List broken references and files containing them, then prompt to fix the errors. This option is engaged by default if none of the reporting options below are engaged. 56 | - `-m --mode` - The type of references to work with. Specify the argument `default` to parse `import` and `require` statements in JavaScript files. Specify `html` to parse `href` and `src` attributes in HTML files. If `-m` is omitted, the `default` mode is used. 57 | - `-n --no-prompts` - Fix errors without prompting (unless overridden by `-r`). 58 | - `-r --report-only` - Don't fix errors, just show the report. (Overrides `-n`.) 59 | 60 | ### Filtering 61 | 62 | All lists of names or globs, as described below, must be separated by commas. If a pattern contains a `*`, the pattern should be should be enclosed in quotes, or the `*` should be escaped (`\*`). 63 | 64 | - `-d --dirs ` - Exclude specified directories from parsing. Provide a list of directory names or globs to exclude. The names are added to the default list of `'.*,node_modules,build'`. 65 | - `-f --files [target_globs]` - Filter the files that are analyzed and repaired. Provide a glob or list of globs to specify the source (referencing) and target (referenced) files that should be included or excluded. For example, `'!*Spec.js,*.js'`. Overrides the default filter, which is `'*.js,*.jsx' '*.*'`. `source_globs` is required. If you wish to filter only target files, enter `''` for `source_globs`. 66 | - `-c --config ` - Load a custom configuration file located at the specified path. This can specify more-advanced parsing options (see below). 67 | 68 | ### Reporting 69 | 70 | When one of the following switches is set, you won't be prompted to repair files unless you include `-e`. 71 | 72 | - `-s --sources` - List files that contain (are sources of) references. 73 | - `-t --targets` - List files that are referenced by (targets of) other files. 74 | 75 | ### Help 76 | 77 | - `-V --version` - Displays the software version. 78 | - `-h --help` - Produces the following display. 79 | ``` 80 | -h, --help output usage information 81 | -V, --version output the version number 82 | -c, --config path of custom configuration file 83 | -m, --mode type of references to look for, JS import/require (default) or HTML href/src (html) 84 | -f, --files [target_globs] filter the source (referencing) and target (referenced) files parsed (e.g., '*1.js,*2.js' 'file.js') 85 | -d, --dirs exclude from sources the specified directories (e.g., 'test,bin') 86 | -e, --errors list broken references and prompt to fix them 87 | -s, --sources list files (sources of references) containing references to other files 88 | -t, --targets list files (targets of references) referenced by other files 89 | -n, --no-prompts fix broken references without prompting (unless -r) 90 | -r, --report-only don't fix broken references, just display reports 91 | ``` 92 | 93 | ## Examples 94 | 95 | `$ ref` 96 | List all broken references and prompt you to fix them. 97 | 98 | `$ ref ../` 99 | Begin parsing in the parent directory and list all broken references and prompt you to fix them. 100 | 101 | `$ ref -ret -d 'test' -f '!*Test.js,!*Spec.js'` 102 | Don't repair files, list errors and all references (targets), and filter out test files and directories. 103 | 104 | `$ ref -n -f '' '**/examples/*Widget.*,**/examples/*Module.*' -- ../src` 105 | Repair files without prompting. Fix only references to widgets and modules in `examples` directory. Set the working directory to `../src`. 106 | 107 | ## Customization 108 | 109 | ### Configuration File 110 | 111 | You can also specify filtering and parsing options by creating a `.reffixrc` file. Place it in your `$home` or `/etc` directory, or specify its path using the command-line option `-c --config`. The file will be merged into the following default settings and must use the same JSON format. You can add as many modes as you want and select them from the command line using the `-m` switch. Each new mode will be merged with `default`, so you need only include the properties you wish to overrride. 112 | 113 | ```javascript 114 | { 115 | "default": { 116 | "workingDirectory": ".", 117 | "directoriesToExclude": [ 118 | ".*", 119 | "node_modules", 120 | "build" 121 | ], 122 | "referencingFileFilter": [ 123 | "*.js", 124 | "*.jsx" 125 | ], 126 | "referencedFileFilter": [ 127 | "*.*" 128 | ], 129 | "searchPatterns": [ 130 | "(^|\\s)import\\s+([\\w\\-\\{\\}\\,\\*\\$\\s]+\\s+)?(['\"]){{valuePattern}}\\3", 131 | "(^|[^\\w-])require *\\( *(['\"]){{valuePattern}}\\2 *\\)" 132 | ], 133 | "valuePattern": "\\.+/[^'\"]+", 134 | "currentDirectoryPrefix": "./", 135 | "textToExclude": [ 136 | "// *[^\\n]+\\n", 137 | "/\\*[\\s\\S]+?\\*/" 138 | ] 139 | }, 140 | "html": { 141 | "referencingFileFilter": [ 142 | "*.html", 143 | "*.js" 144 | ], 145 | "referencedFileFilter": [ 146 | "*.*" 147 | ], 148 | "searchPatterns": [ 149 | "]*href=(['\"]){{valuePattern}}\\1[^>]*>", 150 | "]*href=(['\"]){{valuePattern}}\\1[^>]*>", 151 | "