/** * Provides classes representing filesystem files and folders. */ private import Comments /** A file or folder. */ class Container extends @container { /** * Gets the absolute, canonical path of this container, using forward slashes * as path separator. * * The path starts with a _root prefix_ followed by zero or more _path * segments_ separated by forward slashes. * * The root prefix is of one of the following forms: * * 1. A single forward slash `/` (Unix-style) * 2. An upper-case drive letter followed by a colon and a forward slash, * such as `C:/` (Windows-style) * 3. Two forward slashes, a computer name, and then another forward slash, * such as `//FileServer/` (UNC-style) * * Path segments are never empty (that is, absolute paths never contain two * contiguous slashes, except as part of a UNC-style root prefix). Also, path * segments never contain forward slashes, and no path segment is of the * form `.` (one dot) or `..` (two dots). * * Note that an absolute path never ends with a forward slash, except if it is * a bare root prefix, that is, the path has no path segments. A container * whose absolute path has no segments is always a `Folder`, not a `File`. */ string getAbsolutePath() { none() } /** * Gets a URL representing the location of this container. * * For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls). */ string getURL() { none() } /** * Gets the relative path of this file or folder from the root folder of the * analyzed source location. The relative path of the root folder itself is * the empty string. * * This has no result if the container is outside the source root, that is, * if the root folder is not a reflexive, transitive parent of this container. */ string getRelativePath() { exists(string absPath, string pref | absPath = this.getAbsolutePath() and sourceLocationPrefix(pref) | absPath = pref and result = "" or absPath = pref.regexpReplaceAll("/$", "") + "/" + result and not result.matches("/%") ) } /** * Gets the base name of this container including extension, that is, the last * segment of its absolute path, or the empty string if it has no segments. * * Here are some examples of absolute paths and the corresponding base names * (surrounded with quotes to avoid ambiguity): * * * * * * * * * *
Absolute pathBase name
"/tmp/tst.cs""tst.cs"
"C:/Program Files (x86)""Program Files (x86)"
"/"""
"C:/"""
"D:/"""
"//FileServer/"""
*/ string getBaseName() { result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) } /** * Gets the extension of this container, that is, the suffix of its base name * after the last dot character, if any. * * In particular, * * - if the name does not include a dot, there is no extension, so this * predicate has no result; * - if the name ends in a dot, the extension is the empty string; * - if the name contains multiple dots, the extension follows the last dot. * * Here are some examples of absolute paths and the corresponding extensions * (surrounded with quotes to avoid ambiguity): * * * * * * * * *
Absolute pathExtension
"/tmp/tst.cs""cs"
"/tmp/.classpath""classpath"
"/bin/bash"not defined
"/tmp/tst2."""
"/tmp/x.tar.gz""gz"
*/ string getExtension() { result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) } /** * Gets the stem of this container, that is, the prefix of its base name up to * (but not including) the last dot character if there is one, or the entire * base name if there is not. * * Here are some examples of absolute paths and the corresponding stems * (surrounded with quotes to avoid ambiguity): * * * * * * * * *
Absolute pathStem
"/tmp/tst.cs""tst"
"/tmp/.classpath"""
"/bin/bash""bash"
"/tmp/tst2.""tst2"
"/tmp/x.tar.gz""x.tar"
*/ string getStem() { result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) } /** Gets the parent container of this file or folder, if any. */ Container getParentContainer() { containerparent(result, this) } /** Gets a file or sub-folder in this container. */ Container getAChildContainer() { this = result.getParentContainer() } /** Gets a file in this container. */ File getAFile() { result = this.getAChildContainer() } /** Gets the file in this container that has the given `baseName`, if any. */ File getFile(string baseName) { result = this.getAFile() and result.getBaseName() = baseName } /** Gets a sub-folder in this container. */ Folder getAFolder() { result = this.getAChildContainer() } /** Gets the sub-folder in this container that has the given `baseName`, if any. */ Folder getFolder(string baseName) { result = this.getAFolder() and result.getBaseName() = baseName } /** Gets the file or sub-folder in this container that has the given `name`, if any. */ Container getChildContainer(string name) { result = this.getAChildContainer() and result.getBaseName() = name } /** Gets the file in this container that has the given `stem` and `extension`, if any. */ File getFile(string stem, string extension) { result = this.getAChildContainer() and result.getStem() = stem and result.getExtension() = extension } /** Gets a sub-folder contained in this container. */ Folder getASubFolder() { result = this.getAChildContainer() } /** * Gets a textual representation of the path of this container. * * This is the absolute path of the container. */ string toString() { result = this.getAbsolutePath() } } /** A folder. */ class Folder extends Container, @folder { override string getAbsolutePath() { folders(this, result) } override string getURL() { result = "folder://" + this.getAbsolutePath() } } bindingset[flag] private predicate fileHasExtractionFlag(File f, int flag) { exists(int i | file_extraction_mode(f, i) and i.bitAnd(flag) = flag ) } /** A file. */ class File extends Container, @file { override string getAbsolutePath() { files(this, result) } /** Gets the number of lines in this file. */ int getNumberOfLines() { numlines(this, result, _, _) } /** Gets the number of lines containing code in this file. */ int getNumberOfLinesOfCode() { numlines(this, _, result, _) } /** Gets the number of lines containing comments in this file. */ int getNumberOfLinesOfComments() { numlines(this, _, _, result) } override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } /** Holds if this file is a QL test stub file. */ pragma[noinline] private predicate isStub() { this.extractedQlTest() and this.getAbsolutePath().matches("%resources/stubs/%") } /** Holds if this file contains source code. */ final predicate fromSource() { this.getExtension() = "cs" and not this.isStub() } /** Holds if this file is a library. */ final predicate fromLibrary() { not this.getBaseName() = "" and not this.fromSource() } /** * Holds if this source file came from a PDB. * A source file can come from a PDB and from regular extraction * in the same snapshot. */ predicate isPdbSourceFile() { fileHasExtractionFlag(this, 2) } /** * Holds if this file was extracted using `codeql test run`. */ predicate extractedQlTest() { fileHasExtractionFlag(this, 4) } } /** * A source file. */ class SourceFile extends File { SourceFile() { this.fromSource() } /** Holds if the file was extracted without building the source code. */ predicate extractedStandalone() { fileHasExtractionFlag(this, 1) } }