1+ /**
2+ * Provides classes for working with files and folders.
3+ *
4+ * Stolen liberally from the Javascript QLL
5+ * https://github.com/github/codeql/blob/813d14791d6bea399bc96fa9b7143603eef6e6c4/javascript/ql/src/semmle/javascript/Files.qll
6+ *
7+ */
8+
9+ import ruby
10+
11+ /** A file or folder. */
12+ abstract class Container extends @container {
13+ /**
14+ * Gets the absolute, canonical path of this container, using forward slashes
15+ * as path separator.
16+ *
17+ * The path starts with a _root prefix_ followed by zero or more _path
18+ * segments_ separated by forward slashes.
19+ *
20+ * The root prefix is of one of the following forms:
21+ *
22+ * 1. A single forward slash `/` (Unix-style)
23+ * 2. An upper-case drive letter followed by a colon and a forward slash,
24+ * such as `C:/` (Windows-style)
25+ * 3. Two forward slashes, a computer name, and then another forward slash,
26+ * such as `//FileServer/` (UNC-style)
27+ *
28+ * Path segments are never empty (that is, absolute paths never contain two
29+ * contiguous slashes, except as part of a UNC-style root prefix). Also, path
30+ * segments never contain forward slashes, and no path segment is of the
31+ * form `.` (one dot) or `..` (two dots).
32+ *
33+ * Note that an absolute path never ends with a forward slash, except if it is
34+ * a bare root prefix, that is, the path has no path segments. A container
35+ * whose absolute path has no segments is always a `Folder`, not a `File`.
36+ */
37+ abstract string getAbsolutePath ( ) ;
38+
39+ /**
40+ * Gets a URL representing the location of this container.
41+ *
42+ * For more information see [Providing URLs](https://help.semmle.com/QL/learn-ql/ql/locations.html#providing-urls).
43+ */
44+ abstract string getURL ( ) ;
45+
46+ /**
47+ * Gets the relative path of this file or folder from the root folder of the
48+ * analyzed source location. The relative path of the root folder itself is
49+ * the empty string.
50+ *
51+ * This has no result if the container is outside the source root, that is,
52+ * if the root folder is not a reflexive, transitive parent of this container.
53+ */
54+ string getRelativePath ( ) {
55+ exists ( string absPath , string pref |
56+ absPath = getAbsolutePath ( ) and sourceLocationPrefix ( pref )
57+ |
58+ absPath = pref and result = ""
59+ or
60+ absPath = pref .regexpReplaceAll ( "/$" , "" ) + "/" + result and
61+ not result .matches ( "/%" )
62+ )
63+ }
64+
65+ /**
66+ * Gets the base name of this container including extension, that is, the last
67+ * segment of its absolute path, or the empty string if it has no segments.
68+ *
69+ * Here are some examples of absolute paths and the corresponding base names
70+ * (surrounded with quotes to avoid ambiguity):
71+ *
72+ * <table border="1">
73+ * <tr><th>Absolute path</th><th>Base name</th></tr>
74+ * <tr><td>"/tmp/tst.js"</td><td>"tst.js"</td></tr>
75+ * <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
76+ * <tr><td>"/"</td><td>""</td></tr>
77+ * <tr><td>"C:/"</td><td>""</td></tr>
78+ * <tr><td>"D:/"</td><td>""</td></tr>
79+ * <tr><td>"//FileServer/"</td><td>""</td></tr>
80+ * </table>
81+ */
82+ string getBaseName ( ) { result = getAbsolutePath ( ) .regexpCapture ( ".*/(([^/]*?)(\\.([^.]*))?)" , 1 ) }
83+
84+ /**
85+ * Gets the extension of this container, that is, the suffix of its base name
86+ * after the last dot character, if any.
87+ *
88+ * In particular,
89+ *
90+ * - if the name does not include a dot, there is no extension, so this
91+ * predicate has no result;
92+ * - if the name ends in a dot, the extension is the empty string;
93+ * - if the name contains multiple dots, the extension follows the last dot.
94+ *
95+ * Here are some examples of absolute paths and the corresponding extensions
96+ * (surrounded with quotes to avoid ambiguity):
97+ *
98+ * <table border="1">
99+ * <tr><th>Absolute path</th><th>Extension</th></tr>
100+ * <tr><td>"/tmp/tst.js"</td><td>"js"</td></tr>
101+ * <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
102+ * <tr><td>"/bin/bash"</td><td>not defined</td></tr>
103+ * <tr><td>"/tmp/tst2."</td><td>""</td></tr>
104+ * <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
105+ * </table>
106+ */
107+ string getExtension ( ) {
108+ result = getAbsolutePath ( ) .regexpCapture ( ".*/(([^/]*?)(\\.([^.]*))?)" , 4 )
109+ }
110+
111+ /**
112+ * Gets the stem of this container, that is, the prefix of its base name up to
113+ * (but not including) the last dot character if there is one, or the entire
114+ * base name if there is not.
115+ *
116+ * Here are some examples of absolute paths and the corresponding stems
117+ * (surrounded with quotes to avoid ambiguity):
118+ *
119+ * <table border="1">
120+ * <tr><th>Absolute path</th><th>Stem</th></tr>
121+ * <tr><td>"/tmp/tst.js"</td><td>"tst"</td></tr>
122+ * <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
123+ * <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
124+ * <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
125+ * <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
126+ * </table>
127+ */
128+ string getStem ( ) { result = getAbsolutePath ( ) .regexpCapture ( ".*/(([^/]*?)(\\.([^.]*))?)" , 2 ) }
129+
130+ /** Gets the parent container of this file or folder, if any. */
131+ Container getParentContainer ( ) { containerparent ( result , this ) }
132+
133+ /** Gets a file or sub-folder in this container. */
134+ Container getAChildContainer ( ) { this = result .getParentContainer ( ) }
135+
136+ /** Gets a file in this container. */
137+ File getAFile ( ) { result = getAChildContainer ( ) }
138+
139+ /** Gets the file in this container that has the given `baseName`, if any. */
140+ File getFile ( string baseName ) {
141+ result = getAFile ( ) and
142+ result .getBaseName ( ) = baseName
143+ }
144+
145+ /** Gets a sub-folder in this container. */
146+ Folder getAFolder ( ) { result = getAChildContainer ( ) }
147+
148+ /** Gets the sub-folder in this container that has the given `baseName`, if any. */
149+ Folder getFolder ( string baseName ) {
150+ result = getAFolder ( ) and
151+ result .getBaseName ( ) = baseName
152+ }
153+
154+ /**
155+ * Gets a textual representation of the path of this container.
156+ *
157+ * This is the absolute path of the container.
158+ */
159+ string toString ( ) { result = getAbsolutePath ( ) }
160+ }
161+
162+ /** A folder. */
163+ class Folder extends Container , @folder {
164+ override string getAbsolutePath ( ) { folders ( this , result , _) }
165+
166+ /** Gets the file or subfolder in this folder that has the given `name`, if any. */
167+ Container getChildContainer ( string name ) {
168+ result = getAChildContainer ( ) and
169+ result .getBaseName ( ) = name
170+ }
171+
172+ /** Gets the file in this folder that has the given `stem` and `extension`, if any. */
173+ File getFile ( string stem , string extension ) {
174+ result = getAChildContainer ( ) and
175+ result .getStem ( ) = stem and
176+ result .getExtension ( ) = extension
177+ }
178+
179+ /** Gets a subfolder contained in this folder. */
180+ Folder getASubFolder ( ) { result = getAChildContainer ( ) }
181+
182+ /** Gets the URL of this folder. */
183+ override string getURL ( ) { result = "folder://" + getAbsolutePath ( ) }
184+ }
185+
186+ /** A file. */
187+ class File extends Container , @file {
188+ override string getAbsolutePath ( ) { files ( this , result , _, _, _) }
189+
190+ /** Gets the number of lines in this file. */
191+ int getNumberOfLines ( ) { result = sum ( int loc | numlines ( this , loc , _, _) | loc ) }
192+
193+ /** Gets the number of lines containing code in this file. */
194+ int getNumberOfLinesOfCode ( ) { result = sum ( int loc | numlines ( this , _, loc , _) | loc ) }
195+
196+ /** Gets the number of lines containing comments in this file. */
197+ int getNumberOfLinesOfComments ( ) { result = sum ( int loc | numlines ( this , _, _, loc ) | loc ) }
198+
199+ override string toString ( ) { result = Container .super .toString ( ) }
200+
201+ /** Gets the URL of this file. */
202+ override string getURL ( ) { result = "file://" + this .getAbsolutePath ( ) + ":0:0:0:0" }
203+ }
0 commit comments