11#!/usr/bin/env node
22
33'use strict' ;
4+
45/*
5- * This script analyzes the current commits of the CI.
6- * It will search for blocked statements, which have been added in the commits and fail if present.
6+ * The forbidden identifiers script will check for blocked statements and also detect invalid
7+ * imports of other scope packages.
8+ *
9+ * When running against a PR, the script will only analyze the specific amount of commits inside
10+ * of the Pull Request.
11+ *
12+ * By default it checks all source files and fail if any errors were found.
713 */
814
915const child_process = require ( 'child_process' ) ;
1016const fs = require ( 'fs' ) ;
1117const path = require ( 'path' ) ;
12-
13- const exec = function ( cmd ) {
14- return new Promise ( function ( resolve , reject ) {
15- child_process . exec ( cmd , function ( err , stdout /*, stderr */ ) {
16- if ( err ) {
17- reject ( err ) ;
18- } else {
19- resolve ( stdout ) ;
20- }
21- } ) ;
22- } ) ;
23- } ;
24-
18+ const glob = require ( 'glob' ) . sync ;
2519
2620const blocked_statements = [
2721 '\\bddescribe\\(' ,
@@ -34,64 +28,123 @@ const blocked_statements = [
3428 'from \\\'rxjs/Rx\\\'' ,
3529] ;
3630
37- // Retrieves all scope packages dynamically from the source.
38- // Developers shouldn't be able to import from other scope packages by using relative paths.
39- const componentFolders = fs
40- . readdirSync ( `${ __dirname } /../../src/components` )
41- . map ( componentName => `src/components/${ componentName } ` ) ;
42-
43- const scopePackages = [ 'src/core' ] . concat ( componentFolders ) ;
44-
31+ const sourceFolders = [ './src' , './e2e' ] ;
32+ const scopePackages = [ 'src/core' ] . concat ( glob ( 'src/components/*' ) ) ;
4533const blockedRegex = new RegExp ( blocked_statements . join ( '|' ) , 'g' ) ;
4634const importRegex = / f r o m \s + ' ( .* ) ' ; / g;
4735
48- /**
49- * Find the fork point between HEAD of the current branch, and master .
50- * @return { Promise<string> } A promise which resolves with the fork SHA (or reject) .
36+ /*
37+ * Verify that the current PR is not adding any forbidden identifiers .
38+ * Run the forbidden identifiers check against all sources when not verifying a PR .
5139 */
52- function findForkPoint ( ) {
53- return exec ( 'git merge-base --fork-point HEAD master' )
54- . then ( function ( stdout ) {
55- return stdout . split ( '\n' ) [ 0 ] ;
56- } ) ;
57- }
40+
41+ findTestFiles ( )
42+
43+ /* Only match .js or .ts, and remove .d.ts files. */
44+ . then ( files => files . filter ( name => / \. ( j s | t s ) $ / . test ( name ) && ! / \. d \. t s $ / . test ( name ) ) )
45+
46+ /* Read content of the filtered files */
47+ . then ( files => files . map ( name => ( { name, content : fs . readFileSync ( name , 'utf-8' ) } ) ) )
48+
49+ /* Run checks against content of the filtered files. */
50+ . then ( diffList => {
51+
52+ return diffList . reduce ( ( errors , diffFile ) => {
53+ let fileName = diffFile . name ;
54+ let content = diffFile . content . split ( '\n' ) ;
55+ let lineCount = 0 ;
56+
57+ content . forEach ( line => {
58+ lineCount ++ ;
59+
60+ let matches = line . match ( blockedRegex ) ;
61+ let scopeImport = isRelativeScopeImport ( fileName , line ) ;
62+
63+ if ( matches || scopeImport ) {
64+
65+ let error = {
66+ fileName : fileName ,
67+ lineNumber : lineCount ,
68+ statement : matches && matches [ 0 ] || scopeImport . invalidStatement
69+ } ;
70+
71+ if ( scopeImport ) {
72+ error . messages = [
73+ 'You are using an import statement, which imports a file from another scope package.' ,
74+ `Please import the file by using the following path: ${ scopeImport . scopeImportPath } `
75+ ] ;
76+ }
77+
78+ errors . push ( error ) ;
79+ }
80+ } ) ;
81+
82+ return errors ;
83+
84+ } , [ ] ) ;
85+ } )
86+
87+ /* Print the resolved errors to the console */
88+ . then ( errors => {
89+ if ( errors . length > 0 ) {
90+ console . error ( 'Error: You are using one or more blocked statements:\n' ) ;
91+
92+ errors . forEach ( entry => {
93+ if ( entry . messages ) {
94+ entry . messages . forEach ( message => console . error ( ` ${ message } ` ) )
95+ }
96+
97+ console . error ( ` ${ entry . fileName } @${ entry . lineNumber } , Statement: ${ entry . statement } .\n` ) ;
98+ } ) ;
99+
100+ process . exit ( 1 ) ;
101+ }
102+ } )
103+
104+ . catch ( err => {
105+ // An error occurred in this script. Output the error and the stack.
106+ console . error ( 'An error occurred during execution:' ) ;
107+ console . error ( err . stack || err ) ;
108+ process . exit ( 2 ) ;
109+ } ) ;
110+
58111
59112/**
60- * Get the commit range to evaluate when this script is run .
61- * @return {Promise< string> } A commit range of the form ref1...ref2 .
113+ * Resolves all files, which should run against the forbidden identifiers check .
114+ * @return {Promise.<Array.< string>> } Files to be checked .
62115 */
63- function getCommitRange ( ) {
64- if ( process . env [ 'TRAVIS_COMMIT_RANGE' ] ) {
65- return Promise . resolve ( process . env [ 'TRAVIS_COMMIT_RANGE' ] ) ;
66- } else {
67- return findForkPoint ( ) . then ( ( forkPointSha ) => `${ forkPointSha } ...HEAD` ) ;
116+ function findTestFiles ( ) {
117+ if ( process . env [ 'TRAVIS_PULL_REQUEST' ] ) {
118+ return findChangedFiles ( ) ;
68119 }
120+
121+ var files = sourceFolders . map ( folder => {
122+ return glob ( `${ folder } /**/*.ts` ) ;
123+ } ) . reduce ( ( files , fileSet ) => files . concat ( fileSet ) , [ ] ) ;
124+
125+ return Promise . resolve ( files ) ;
69126}
70127
71128/**
72129 * List all the files that have been changed or added in the last commit range.
73- * @returns {Promise<Array<string>> } Resolves with a list of files that are
74- * added or changed.
130+ * @returns {Promise.<Array.<string>> } Resolves with a list of files that are added or changed.
75131 */
76132function findChangedFiles ( ) {
77- return getCommitRange ( )
78- . then ( range => {
79- return exec ( `git diff --name-status ${ range } ./src ./e2e` ) ;
80- } )
133+ let commitRange = process . env [ 'TRAVIS_COMMIT_RANGE' ] ;
134+
135+ return exec ( `git diff --name-status ${ commitRange } ${ sourceFolders . join ( ' ' ) } ` )
81136 . then ( rawDiff => {
82- // Ignore deleted files.
83- return rawDiff . split ( '\n' )
84- . filter ( function ( line ) {
85- // Status: C## => Copied (##% confident)
86- // R## => Renamed (##% confident)
87- // D => Deleted
88- // M => Modified
89- // A => Added
90- return line . match ( / ( [ C R ] [ 0 - 9 ] * | [ A M ] ) \s + / ) ;
91- } )
92- . map ( function ( line ) {
93- return line . split ( / \s + / , 2 ) [ 1 ] ;
94- } ) ;
137+ return rawDiff
138+ . split ( '\n' )
139+ . filter ( line => {
140+ // Status: C## => Copied (##% confident)
141+ // R## => Renamed (##% confident)
142+ // D => Deleted
143+ // M => Modified
144+ // A => Added
145+ return line . match ( / ( [ C R ] [ 0 - 9 ] * | [ A M ] ) \s + / ) ;
146+ } )
147+ . map ( line => line . split ( / \s + / , 2 ) [ 1 ] ) ;
95148 } ) ;
96149}
97150
@@ -125,103 +178,53 @@ function isRelativeScopeImport(fileName, line) {
125178 // Transform the relative import path into an absolute path.
126179 importPath = path . resolve ( path . dirname ( fileName ) , importPath ) ;
127180
128- let currentScope = findScope ( fileName ) ;
181+ let fileScope = findScope ( fileName ) ;
129182 let importScope = findScope ( importPath ) ;
130183
131- if ( currentScope !== importScope ) {
132- // Transforms the invalid import statement into a correct import statement, which uses the scope package.
133- let scopeImport = `@angular2-material/${ path . basename ( importScope ) } /${ path . relative ( importScope , importPath ) } ` ;
184+ if ( fileScope . path !== importScope . path ) {
185+
186+ // Creates a valid import statement, which uses the correct scope package.
187+ let importFilePath = path . relative ( importScope . path , importPath ) ;
188+ let validImportPath = `@angular2-material/${ importScope . name } /${ importFilePath } ` ;
134189
135190 return {
136- currentScope : currentScope ,
137- importScope : importScope ,
191+ fileScope : fileScope . name ,
192+ importScope : importScope . name ,
138193 invalidStatement : importMatch [ 0 ] ,
139- scopeImportPath : scopeImport
194+ scopeImportPath : validImportPath
140195 }
141196 }
142197
143- return false ;
144-
145198 function findScope ( filePath ) {
146- // Normalize the filePath as well , to avoid issues with the different environments path delimiter.
199+ // Normalize the filePath, to avoid issues with the different environments path delimiter.
147200 filePath = path . normalize ( filePath ) ;
148201
149- // Iterate through all scopes and try to find them inside of the file path.
150- return scopePackages . filter ( scope => filePath . indexOf ( path . normalize ( scope ) ) !== - 1 ) . pop ( ) ;
202+ // Iterate through all scope paths and try to find them inside of the file path.
203+ var scopePath = scopePackages
204+ . filter ( scope => filePath . indexOf ( path . normalize ( scope ) ) !== - 1 )
205+ . pop ( ) ;
206+
207+ // Return an object containing the name of the scope and the associated path.
208+ return {
209+ name : path . basename ( scopePath ) ,
210+ path : scopePath
211+ }
151212 }
152213
153214}
154215
155-
156- // Find all files, check for errors, and output every errors found.
157- findChangedFiles ( )
158- . then ( fileList => {
159- // Only match .js or .ts, and remove .d.ts files.
160- return fileList . filter ( function ( name ) {
161- return name . match ( / \. [ j t ] s $ / ) && ! name . match ( / \. d \. t s $ / ) ;
216+ /**
217+ * Executes a process command and wraps it inside of a promise.
218+ * @returns {Promise.<String> }
219+ */
220+ function exec ( cmd ) {
221+ return new Promise ( function ( resolve , reject ) {
222+ child_process . exec ( cmd , function ( err , stdout /*, stderr */ ) {
223+ if ( err ) {
224+ reject ( err ) ;
225+ } else {
226+ resolve ( stdout ) ;
227+ }
162228 } ) ;
163- } )
164- . then ( fileList => {
165- // Read every file and return a Promise that will contain an array of
166- // Object of the form { fileName, content }.
167- return Promise . all ( fileList . map ( function ( fileName ) {
168- return {
169- fileName : fileName ,
170- content : fs . readFileSync ( fileName , { encoding : 'utf-8' } )
171- } ;
172- } ) ) ;
173- } )
174- . then ( diffList => {
175- // Reduce the diffList to an array of errors. The array will be empty if no errors
176- // were found.
177- return diffList . reduce ( ( errors , diffEntry ) => {
178- let fileName = diffEntry . fileName ;
179- let content = diffEntry . content . split ( '\n' ) ;
180- let ln = 0 ;
181-
182- // Get all the lines that start with `+`.
183- content . forEach ( function ( line ) {
184- ln ++ ;
185-
186- let matches = line . match ( blockedRegex ) ;
187- let isScopeImport = isRelativeScopeImport ( fileName , line ) ;
188-
189- if ( matches || isScopeImport ) {
190- // Accumulate all errors at once.
191- let error = {
192- fileName : fileName ,
193- lineNumber : ln ,
194- statement : matches && matches [ 0 ] || isScopeImport . invalidStatement
195- } ;
196-
197- if ( isScopeImport ) {
198- error . message =
199- ' You are using an import statement, which imports a file from another scope package.\n' +
200- ` Please import the file by using the following path: ${ isScopeImport . scopeImportPath } ` ;
201- }
202-
203- errors . push ( error ) ;
204- }
205- } ) ;
206- return errors ;
207- } , [ ] ) ;
208- } )
209- . then ( errors => {
210- if ( errors . length > 0 ) {
211- console . error ( 'Error: You are using a statement in your commit, which is not allowed.\n' ) ;
212-
213- errors . forEach ( entry => {
214- if ( entry . message ) console . error ( entry . message ) ;
215- console . error ( ` ${ entry . fileName } @${ entry . lineNumber } , Statement: ${ entry . statement } .\n` ) ;
216- } ) ;
217-
218- process . exit ( 1 ) ;
219- }
220- } )
221- . catch ( err => {
222- // An error occured in this script. Output the error and the stack.
223- console . error ( 'An error occured during execution:' ) ;
224- console . error ( err ) ;
225- console . error ( err . stack ) ;
226- process . exit ( 2 ) ;
227229 } ) ;
230+ }
0 commit comments