1
1
#!/usr/bin/env node
2
2
3
3
'use strict' ;
4
+
4
5
/*
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.
7
13
*/
8
14
9
15
const child_process = require ( 'child_process' ) ;
10
16
const fs = require ( 'fs' ) ;
11
17
const 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 ;
25
19
26
20
const blocked_statements = [
27
21
'\\bddescribe\\(' ,
@@ -34,64 +28,123 @@ const blocked_statements = [
34
28
'from \\\'rxjs/Rx\\\'' ,
35
29
] ;
36
30
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/*' ) ) ;
45
33
const blockedRegex = new RegExp ( blocked_statements . join ( '|' ) , 'g' ) ;
46
34
const importRegex = / f r o m \s + ' ( .* ) ' ; / g;
47
35
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 .
51
39
*/
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
+
58
111
59
112
/**
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 .
62
115
*/
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 ( ) ;
68
119
}
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 ) ;
69
126
}
70
127
71
128
/**
72
129
* 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.
75
131
*/
76
132
function 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 ( ' ' ) } ` )
81
136
. 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 ] ) ;
95
148
} ) ;
96
149
}
97
150
@@ -125,103 +178,53 @@ function isRelativeScopeImport(fileName, line) {
125
178
// Transform the relative import path into an absolute path.
126
179
importPath = path . resolve ( path . dirname ( fileName ) , importPath ) ;
127
180
128
- let currentScope = findScope ( fileName ) ;
181
+ let fileScope = findScope ( fileName ) ;
129
182
let importScope = findScope ( importPath ) ;
130
183
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 } ` ;
134
189
135
190
return {
136
- currentScope : currentScope ,
137
- importScope : importScope ,
191
+ fileScope : fileScope . name ,
192
+ importScope : importScope . name ,
138
193
invalidStatement : importMatch [ 0 ] ,
139
- scopeImportPath : scopeImport
194
+ scopeImportPath : validImportPath
140
195
}
141
196
}
142
197
143
- return false ;
144
-
145
198
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.
147
200
filePath = path . normalize ( filePath ) ;
148
201
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
+ }
151
212
}
152
213
153
214
}
154
215
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
+ }
162
228
} ) ;
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 ) ;
227
229
} ) ;
230
+ }
0 commit comments