diff --git a/.travis.yml b/.travis.yml index 4e6e4b8..4f25546 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ +before_install: + - nvm install 8 + +install: + - npm install + language: node_js -node_js: - - "node" - - "6" -script: - - npm test + +script: + - npm run compile - npm run lint + - npm run jest \ No newline at end of file diff --git a/README.md b/README.md index 1b27c3f..c500aa9 100644 --- a/README.md +++ b/README.md @@ -30,37 +30,42 @@ By default, `mcritic` will recursively search the current and parent folders for ```json { - "callToImportRegex": "(\\S+)", - "callToImportReplace": "{$1|param}" + "callToImport": [ + { + "regex": "(\\S+)", + "replace": "{$1|param}" + } + ] } ``` Here are the currently supported **settings**: -### `callToImportRegex` +### `callToImport` -This property is used to provide matches for `callToImportReplace`. This should be a standard javascript regex that contains capture groups to be referenced in `callToImportReplace`. +This property is used to provide a list of replacement configurations. Each list item must contain two properties: -|Type|Default| -|----|-------| -|string|`"(.*)"`| +- `regex` + + This property is used to provide matches for `replace`. This should be a standard javascript regex that contains capture groups to be referenced in `replace`. + +- `replace` -### `callToImportReplace` + This property uses match groups defined in `regex` to translate a component name in a soy template to its corresponding import name when validating their import. -This property uses match groups defined in `callToImportRegex` to translate a component name in a soy template to its corresponding import name when validating their import. + When referencing match groups from `regex`, interpolation should be in the form of `{$n}`, where `n` is the match group number. An example would be `"{$1}.js"`. -When referencing match groups from `callToImportRegex`, interpolation should be in the form of `{$n}`, where `n` is the match group number. An example would be `"{$1}.js"`. + Interpolations can also contain named string transformations delimited by a `|`. This transformation corresponds to the functions provided by [`change-case`](https://www.npmjs.com/package/change-case). -Interpolations can also contain named string transformations delimited by a `|`. This transformation corresponds to the functions provided by [`change-case`](https://www.npmjs.com/package/change-case). + Examples: + * `"{$1|lower|snake}.js"` + * `"{$2}-{$1}"` + * `"{$1|dot}.js"` -Examples: -* `"{$1|lower|snake}.js"` -* `"{$2}-{$1}"` -* `"{$1|dot}.js"` |Type|Default| |----|-------| -|string|`"{$1}"`| +|array|`"[{"regex": "(.*)", "replace": "{$1}"}]"`| ### `implicitParams` diff --git a/package.json b/package.json index 4e8a830..8f69346 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,17 @@ "engines": { "node": ">=6.4.0" }, - "repository": "https://github.com/mthadley/metal-soy-critic", + "repository": "https://github.com/metal/metal-soy-critic", "main": "./lib/index.js", "files": [ "lib" ], "scripts": { - "lint": "tslint src/**/*.ts", - "test": "npm run compile && jest", "compile": "tsc", - "prepublish": "npm run compile" + "jest": "jest", + "lint": "tslint src/**/*.ts", + "prepublish": "npm run compile", + "test": "npm run compile && jest" }, "jest": { "moduleFileExtensions": [ diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap index f7b1e9e..02ef876 100644 --- a/src/__tests__/__snapshots__/index.ts.snap +++ b/src/__tests__/__snapshots__/index.ts.snap @@ -7,7 +7,7 @@ exports[`cli should accept an ignore glob 1`] = ` `; exports[`cli should fail with invalid config file 1`] = ` -"Failed to read config: callToImportRegex is not a valid RegExp. +"Failed to read config: callToImport.regex \\"(\\\\S+\\" is not a valid RegExp. " `; diff --git a/src/__tests__/config.ts b/src/__tests__/config.ts index 3863d72..e56ec93 100644 --- a/src/__tests__/config.ts +++ b/src/__tests__/config.ts @@ -35,7 +35,7 @@ describe('config', () => { const config = Config.readConfig(); expect(config).toBeInstanceOf(Object); - expect(config.callToImportRegex).toBe('(.*)'); + expect(config.callToImport).toEqual([{regex: '(.*)', replace: '{$1}'}]); }); it('should return a configuration specified in a file', () => { @@ -43,7 +43,7 @@ describe('config', () => { const config = Config.readConfig(); - expect(config.callToImportRegex).toBe('(\\S+)'); + expect(config.callToImport).toEqual([{regex: '(\\S+)', replace: '{$1|param}'}]); }); it('should read config files with a json extension', () => { @@ -51,7 +51,15 @@ describe('config', () => { const config = Config.readConfig(); - expect(config.callToImportRegex).toBe('json'); + expect(config.callToImport).toEqual([{regex: 'json', replace: '{$1|param}'}]); + }); + + it('should return a converted configuration specified in a file if is found in the old way', () => { + process.chdir('./test/fixtures/deprecated-config'); + + const config = Config.readConfig(); + + expect(config.callToImport).toEqual([{regex: '(\\S+)', replace: '{$1|param}'}]); }); }); @@ -70,8 +78,12 @@ describe('config', () => { it('should throw an Error if the config is invalid', () => { const invalidConfig = { - callToImportRegex: '(asd', - callToImportReplace: 'bar', + callToImport: [ + { + regex: '(asd', + replace: 'bar', + }, + ], implicitParams: {}, }; diff --git a/src/config.ts b/src/config.ts index b11c3c3..6e55669 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,4 @@ +import * as chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path'; import * as process from 'process'; @@ -7,29 +8,38 @@ const CONFIG_FILE_NAMES = [ '.soycriticrc.json', ]; +export interface CallToImportConfig { + regex: string + replace: string +} + export interface ImplicitParamsMap { [nameOrRegex: string]: string | Array } export interface Config { - callToImportRegex: string - callToImportReplace: string + callToImport: Array implicitParams: ImplicitParamsMap } export const DEFAULT_CONFIG: Config = { - callToImportRegex: '(.*)', - callToImportReplace: '{$1}', + callToImport: [{regex: '(.*)',replace: '{$1}'}], implicitParams: {} }; export function validateConfig(config: Config): Config { - if (!isRegex(config.callToImportRegex)) { - throw new Error('callToImportRegex is not a valid RegExp.'); + if (!Array.isArray(config.callToImport)) { + throw new Error('callToImport is not a valid config array.'); } - if (!isRegex(config.callToImportReplace)) { - throw new Error('callToImportReplace is not a valid replace string.'); + for (const item of config.callToImport) { + if (!isRegex(item.regex)) { + throw new Error(`callToImport.regex "${item.regex}" is not a valid RegExp.`); + } + + if (!isRegex(item.replace)) { + throw new Error(`callToImport.replace "${item.replace}" is not a valid replace string.`); + } } for (const key in config.implicitParams) { @@ -41,6 +51,22 @@ export function validateConfig(config: Config): Config { return config; } +export function convertConfig(config: any): Config { + if (config.callToImportRegex && config.callToImportReplace) { + config.callToImport = [ + { + regex: config.callToImportRegex, + replace: config.callToImportReplace + } + ]; + + console.log(chalk.yellow('CONFIG API HAS CHANGED, PLEASE UPDATE\n')); + console.log('\tYour callToImport configuration is outdated, update it to new API.\n'); + } + + return config; +} + export function readConfig(): Config { const filePath = getConfigFilePath(); let config = {}; @@ -50,7 +76,9 @@ export function readConfig(): Config { config = JSON.parse(buffer.toString('utf8')); } - + + config = convertConfig(config); + return validateConfig({...DEFAULT_CONFIG, ...config}); } @@ -78,4 +106,4 @@ export function isRegex(regex: string): boolean { return false; } return true; -} +} \ No newline at end of file diff --git a/src/validate-call-imports.ts b/src/validate-call-imports.ts index 04abe23..aa3b2bf 100644 --- a/src/validate-call-imports.ts +++ b/src/validate-call-imports.ts @@ -25,19 +25,30 @@ function getImportPaths(ast: T.Node): Array { jsTraverse(ast, { ImportDeclaration(path) { importPaths.push(path.node.source.value); + }, + ImportSpecifier(path) { + importPaths.push(path.node.imported.name); } }); + return importPaths; } export default function valdiateCallImports(soyContext: SoyContext, jsContext: JSContext, config: Config): Result { const importNames = getImportPaths(jsContext.ast) .map(importPath => path.parse(importPath).name); - - const missingImports = getExternalSoyCalls(soyContext) + + const missingImports = getExternalSoyCalls(soyContext) .filter(name => { - name = transform(name, config.callToImportRegex, config.callToImportReplace); - return !importNames.find(importName => importName.includes(name)); + for (const item of config.callToImport) { + let transformedName = transform(name, item.regex, item.replace); + + if (importNames.find(importName => importName.includes(transformedName))) { + return false; + } + } + + return true; }); if (missingImports.length) { diff --git a/test/fixtures/CaseImport.js b/test/fixtures/CaseImport.js index 0e434c9..7f8db61 100644 --- a/test/fixtures/CaseImport.js +++ b/test/fixtures/CaseImport.js @@ -2,6 +2,7 @@ import Component from 'metal-component'; import Soy from 'metal-soy'; import 'OtherComponent'; +import {OtherSubComponent} from 'OtherComponent'; import templates from './CaseImport.soy'; import {Config} from 'metal-state'; diff --git a/test/fixtures/CaseImport.soy b/test/fixtures/CaseImport.soy index a5452ed..77c968a 100644 --- a/test/fixtures/CaseImport.soy +++ b/test/fixtures/CaseImport.soy @@ -8,4 +8,8 @@ {call OtherComponent.render} {param foo: 'bar' /} {/call} + + {call OtherSubComponent.render} + {param foo: 'bar' /} + {/call} {/template} diff --git a/test/fixtures/config-json/.soycriticrc.json b/test/fixtures/config-json/.soycriticrc.json index bd7e31b..3e729f2 100644 --- a/test/fixtures/config-json/.soycriticrc.json +++ b/test/fixtures/config-json/.soycriticrc.json @@ -1,4 +1,8 @@ { - "callToImportRegex": "json", - "callToImportReplace": "{$1|param}" + "callToImport": [ + { + "regex": "json", + "replace": "{$1|param}" + } + ] } diff --git a/test/fixtures/config/.soycriticrc b/test/fixtures/config/.soycriticrc index 399fd9d..d0d5e2f 100644 --- a/test/fixtures/config/.soycriticrc +++ b/test/fixtures/config/.soycriticrc @@ -1,4 +1,8 @@ { - "callToImportRegex": "(\\S+)", - "callToImportReplace": "{$1|param}" + "callToImport": [ + { + "regex": "(\\S+)", + "replace": "{$1|param}" + } + ] } diff --git a/test/fixtures/deprecated-config/.soycriticrc b/test/fixtures/deprecated-config/.soycriticrc new file mode 100644 index 0000000..ac42791 --- /dev/null +++ b/test/fixtures/deprecated-config/.soycriticrc @@ -0,0 +1,4 @@ +{ + "callToImportRegex": "(\\S+)", + "callToImportReplace": "{$1|param}" +} \ No newline at end of file diff --git a/test/fixtures/invalid-config/.soycriticrc b/test/fixtures/invalid-config/.soycriticrc index bac036a..86ed256 100644 --- a/test/fixtures/invalid-config/.soycriticrc +++ b/test/fixtures/invalid-config/.soycriticrc @@ -1,4 +1,8 @@ { - "callToImportRegex": "(\\S+", - "callToImportReplace": "{$1|param}" + "callToImport": [ + { + "regex": "(\\S+", + "replace": "{$1|param}" + } + ] }