// Copyright Epic Games, Inc. All Rights Reserved. #include "uLang/SourceProject/SourceFileProject.h" #include "uLang/SourceProject/SourceProjectWriter.h" #include "uLang/Common/Text/FilePathUtils.h" #include "uLang/Common/Templates/Storage.h" #include "uLang/JSON/JSON.h" #include "uLang/SourceProject/SourceProjectUtils.h" #include "uLang/SourceProject/VerseVersion.h" // #SUPPORT_LEGACY_VMODULES // Temporary switch allowing legacy named vmodule files // until we have converted all Verse code to new directory-driven module hierarchy #define VERSE_ALLOW_VMODULE_FILES 1 namespace uLang { //==================================================================================== // CSourceFileSnippet implementation //==================================================================================== TOptional CSourceFileSnippet::GetText() const { // If a modified version exists, return that if (_ModifiedText) { return *_ModifiedText; } // Otherwise fetch current version from disk CUTF8StringBuilder FileContents; const bool bReadSuccess = _FileSystem->FileRead(_FilePath.AsCString(), [&FileContents](size_t ByteSize) { return (void*)FileContents.AppendBuffer(ByteSize); }); if (bReadSuccess) { const CUTF8StringView& ContentsView = FileContents.ToStringView(); const int32_t ByteSize = FileContents.ByteLen(); if (ByteSize >= 2 && !(ByteSize & 1) && ((ContentsView[0] == 0xff && ContentsView[1] == 0xfe) || (ContentsView[0] == 0xfe && ContentsView[1] == 0xff))) { // It's UTF-16 - we don't support that return uLang::EResult::Error; } if (ByteSize >= 3 && ContentsView[0] == 0xef && ContentsView[1] == 0xbb && ContentsView[2] == 0xbf) { // Found UTF-8 BOM, trim it away return CUTF8String(ContentsView.SubViewTrimBegin(3)); } return FileContents.MoveToString(); } return uLang::EResult::Error; } //==================================================================================== // CSourceFileModule implementation //==================================================================================== CUTF8StringView CSourceFileModule::GetDirPath() const { CUTF8StringView DirPath, FileName; FilePathUtils::SplitPath(_FilePath, DirPath, FileName); return DirPath; } TOptional> CSourceFileModule::FindSubmodule(const CUTF8StringView& ModuleName) const { TOptional> Module = _Submodules.FindByKey(ModuleName); return reinterpret_cast>&>(Module); } TSRef CSourceFileModule::FindOrAddSubmodule(const CUTF8StringView& ModuleName, const CUTF8StringView& DirPath) { TOptional> Module = FindSubmodule(ModuleName); if (!Module) { Module = TSRef::New(ModuleName, DirPath); _Submodules.Add(*Module); } return *Module; } TOptional> CSourceFileModule::FindSnippetByFilePath(const CUTF8StringView& FilePath, bool bRecursive) const { TOptional> FoundSnippet; VisitAll([&FilePath, bRecursive, &FoundSnippet](const CSourceModule& Module) { const CUTF8StringView& ModulePath = static_cast(Module).GetDirPath(); if (ModulePath.IsEqualCaseIndependent(FilePath.SubViewBegin(ModulePath.ByteLen()))) { FoundSnippet = Module._SourceSnippets.FindByPredicate([&FilePath](const ISourceSnippet* Snippet) { return static_cast(Snippet)->GetFilePath().IsEqualCaseIndependent(FilePath); }); } return bRecursive && !FoundSnippet; }); return reinterpret_cast>&>(FoundSnippet); } //==================================================================================== // CSourceFilePackage implementation //==================================================================================== bool FromJSON(const JSONValue& JSON, EVerseScope* Value) { if (JSON.IsString()) { const CUTF8StringView ValueString(JSON.GetString(), JSON.GetStringLength()); if (ValueString == "PublicAPI") { *Value = EVerseScope::PublicAPI; return true; } if (ValueString == "InternalAPI") { *Value = EVerseScope::InternalAPI; return true; } if (ValueString == "PublicUser") { *Value = EVerseScope::PublicUser; return true; } if (ValueString == "InternalUser") { *Value = EVerseScope::InternalUser; return true; } } return false; } bool FromJSON(const JSONValue& JSON, EPackageRole* Value) { if (JSON.IsString()) { const CUTF8StringView ValueString(JSON.GetString(), JSON.GetStringLength()); TOptional MaybeRole = ToPackageRole(ValueString); if (MaybeRole.IsSet()) { *Value = *MaybeRole; return true; } } return false; } bool FromJSON(const JSONValue& JSON, CSourcePackage::SSettings* Value) { return FromJSON(JSON, "versePath", &Value->_VersePath, false) && FromJSON(JSON, "verseScope", &Value->_VerseScope, false) && FromJSON(JSON, "dependencyPackages", &Value->_DependencyPackages, false) && FromJSON(JSON, "role", &Value->_Role, false) && FromJSON(JSON, "verseVersion", &Value->_VerseVersion, false) && FromJSON(JSON, "treatModulesAsImplicit", &Value->_bTreatModulesAsImplicit, false) && FromJSON(JSON, "vniDestDir", &Value->_VniDestDir, false) && FromJSON(JSON, "allowExperimental", &Value->_bAllowExperimental, false); } CSourceFilePackage::CSourceFilePackage(const CUTF8String& PackageFilePath, const TSRef& FileSystem, const TSRef& Diagnostics) : CSourcePackage(FilePathUtils::GetNameFromFileOrDir(PackageFilePath), TSRef::New("", FilePathUtils::GetDirectory(PackageFilePath, true))) , _PackageFilePath(PackageFilePath) , _DirPath(_RootModule->GetFilePath()) , _FileSystem(FileSystem) { // Load package file from disk ReadPackageFile(PackageFilePath, Diagnostics); // Gather modules and snippets GatherPackageSourceFiles(PackageFilePath, FileSystem, Diagnostics); } CSourceFilePackage::CSourceFilePackage(const SPackageDesc& PackageDesc, const TSRef& FileSystem, const TSRef& Diagnostics) : CSourcePackage(PackageDesc._Name, TSRef::New("", FilePathUtils::AppendSlash(PackageDesc._DirPath))) , _DirPath(PackageDesc._DirPath) , _FilePaths(PackageDesc._FilePaths) , _FileSystem(FileSystem) { _Settings = PackageDesc._Settings; // Gather modules and snippets GatherPackageSourceFiles(PackageDesc._Name, FileSystem, Diagnostics); } CSourceFileModule* CSourceFilePackage::GetModuleForFilePath(const CUTF8StringView& FilePath) { CSourceFileModule* Result = nullptr; _RootModule->VisitAll([&FilePath, &Result](CSourceModule& Module)->bool { CSourceFileModule& FileModule = static_cast(Module); const CUTF8StringView& ModulePath = FileModule.GetDirPath(); if (FilePath.ByteLen() > ModulePath.ByteLen()) { if (ModulePath == FilePathUtils::GetDirectory(FilePath)) { Result = &FileModule; return false; } } return true; }); return Result; } TOptional> CSourceFilePackage::FindSnippetByFilePath(const CUTF8StringView& FilePath) const { TOptional> Result = _RootModule.As()->FindSnippetByFilePath(FilePath, true); if (Result) { return Result; } if (_Digest.IsSet() && _Digest->_Snippet.As()->GetFilePath().IsEqualCaseIndependent(FilePath)) { return _Digest->_Snippet.As(); } if (_PublicDigest.IsSet() && _PublicDigest->_Snippet.As()->GetFilePath().IsEqualCaseIndependent(FilePath)) { return _PublicDigest->_Snippet.As(); } return EResult::Unspecified; } TOptional> CSourceFilePackage::AddSnippet(const CUTF8StringView& FilePath) { CSourceFileModule* Module = GetModuleForFilePath(FilePath); if (Module) { TSRef Snippet = TSRef::New(FilePath, _FileSystem); Module->AddSnippet(Snippet); return Snippet; } return EResult::Error; } bool CSourceFilePackage::RemoveSnippet(const CUTF8StringView& FilePath) { CSourceFileModule* Module = GetModuleForFilePath(FilePath); if (Module) { TOptional> Snippet = Module->FindSnippetByFilePath(FilePath, false); if (Snippet) { Module->RemoveSnippet(*Snippet, false); return true; } } return false; } void CSourceFilePackage::ReadPackageFile(const CUTF8String& PackageFilePath, const TSRef& Diagnostics) { // Parse package file and settings if (!CSourceFileProject::IsPackageFile(PackageFilePath)) { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_BadPackageFileName, CUTF8String("Package file `%s` has incorrect file extension.", *PackageFilePath) }); } CUTF8StringBuilder PackageFileContents; const bool bReadSuccess = _FileSystem->FileRead(PackageFilePath.AsCString(), [&PackageFileContents](size_t ByteSize) { return (void*)PackageFileContents.AppendBuffer(ByteSize); }); if (bReadSuccess) { // Set up RapidJSON document JSONAllocator Allocator; JSONMemoryPoolAllocator MemoryPoolAllocator(RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY, &Allocator); const size_t StackCapacity = 1024; JSONDocument PackageDocument(&MemoryPoolAllocator, StackCapacity, &Allocator); // Parse package file into document PackageDocument.Parse(PackageFileContents.AsCString(), PackageFileContents.ByteLen()); if (!PackageDocument.HasParseError() && FromJSON(PackageDocument, &_Settings)) { if (_Settings._VniDestDir) { // Fully qualify VNI directory _Settings._VniDestDir.Emplace(FilePathUtils::ConvertRelativePathToFull(*_Settings._VniDestDir, _DirPath)); } } else { Diagnostics->AppendGlitch({ EDiagnostic::ErrSyntax_MalformedPackageFile, CUTF8String("Cannot parse contents of package file `%s`.", *PackageFilePath) }); } } else { Diagnostics->AppendGlitch({ EDiagnostic::WarnSystem_CannotReadPackage, CUTF8String("Unable to read package file `%s`.", *PackageFilePath) }); } } TSRef CSourceFilePackage::ResolveModuleForRelativeVersePath(const CUTF8String& RelativeVersePath, const TSRef& Diagnostics) const { TSRef Module = _RootModule.As(); FilePathUtils::ForeachPartOfPath(RelativeVersePath, [&Module, &RelativeVersePath, &Diagnostics](const CUTF8StringView& Part) { if (Part.IsFilled()) // If the path component is relative, just check the next component of the path. { if (Part == ".." || Part == ".") { return; } if (CSourceFileProject::IsValidModuleName(Part)) { Module = Module->FindOrAddSubmodule(Part, FilePathUtils::AppendSlash(FilePathUtils::CombinePaths(Module->GetDirPath(), Part))); } else { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_InvalidModuleName, CUTF8String("The relative Verse path `%s` contains disallowed characters that would lead to the invalid module name `%s`.", *RelativeVersePath, *CUTF8String(Part)) }); } } }); return Module; } void CSourceFilePackage::GatherPackageSourceFiles(const CUTF8String& PackageFilePath, const TSRef& FileSystem, const TSRef& Diagnostics) { TArray SourceFilePaths; TArray StrayPackageFilePaths; // Additional package files found underneath package // Helper to process a single file discovered on disk auto ProcessFile = [this, &PackageFilePath, &StrayPackageFilePaths, &Diagnostics, &FileSystem](const CUTF8String& FilePath) { CUTF8String NormalizedFilePath = FilePathUtils::NormalizePath(FilePath); bool bIsSnippetFile = CSourceFileProject::IsSnippetFile(NormalizedFilePath); bool bIsModuleFile = CSourceFileProject::IsModuleFile(NormalizedFilePath); if (bIsSnippetFile || bIsModuleFile) { // Find or create module for this file CUTF8String RelativeFilePath = FilePathUtils::ConvertFullPathToRelative(NormalizedFilePath, _DirPath); if (!ULANG_ENSUREF(RelativeFilePath != NormalizedFilePath, "File path `%s` appears to be not under package directory `%s`", *NormalizedFilePath, *_DirPath)) { return; } if (bIsSnippetFile) { TSRef Snippet = TSRef::New(Move(NormalizedFilePath), FileSystem); if (Snippet->GetFilePath().EndsWith(".digest.verse")) { if (_Digest.IsSet()) { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_DuplicateDigestFile, CUTF8String("Found duplicate digest `%s` for package `%s` when digest `%s` already exists.", *Snippet->GetPath(), *GetName(), *_Digest->_Snippet->GetPath())}); } else { _Digest.Emplace(SVersionedDigest{Move(Snippet), _Settings._VerseVersion.Get(Verse::Version::Default)}); } } else { bool bIsVNIPackage = _Settings._VniDestDir.IsSet(); bool bHasNativeFileExtension = Snippet->GetFilePath().EndsWith(".native.verse"); CUTF8StringView Dir; CUTF8StringView FileName; FilePathUtils::SplitPath(Snippet->GetFilePath(), Dir, FileName); CUTF8StringView Stem; CUTF8StringView Extension; FilePathUtils::SplitFileName(FileName, Stem, Extension); if (!VerseFN::UploadedAtFNVersion::EnforceSnippetNameValidity(_Settings._UploadedAtFNVersion.Get(VerseFN::UploadedAtFNVersion::Latest)) && !bHasNativeFileExtension) { if (Stem.Contains('.')) { return; } } if (VerseFN::UploadedAtFNVersion::EnforceSnippetNameValidity(_Settings._UploadedAtFNVersion.Get(VerseFN::UploadedAtFNVersion::Latest)) && !CSourceFileProject::IsValidSnippetFileName(FileName)) { Diagnostics->AppendGlitch({EDiagnostic::ErrSystem_BadSnippetFileName, CUTF8String("Verse file `%s` does not have a valid snippet name. Verse snippet names must end in `.verse` and cannot contain any of the following characters: %s.", *Snippet->GetPath(), CSourceFileSnippet::_InvalidSnippetCharacters)}); } if (bIsVNIPackage && !bHasNativeFileExtension) { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_InconsistentNativeFileExtension, CUTF8String("Verse file `%s` is in VNI-capable package `%s`, therefore should have the `.native.verse` file extension.", *Snippet->GetPath(), *GetName()) }); } else if (!bIsVNIPackage && bHasNativeFileExtension) { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_InconsistentNativeFileExtension, CUTF8String("Verse file `%s` is in non-VNI-capable package `%s`, therefore should not have the `.native.verse` file extension.", *Snippet->GetPath(), *GetName()) }); } TSRef Module = ResolveModuleForRelativeVersePath(FilePathUtils::GetDirectory(RelativeFilePath), Diagnostics); Module->AddSnippet(Snippet); } } else { ULANG_ASSERTF(bIsModuleFile, "Must be a module when we get here."); #if VERSE_ALLOW_VMODULE_FILES // Find module based on its path TSRef Module = ResolveModuleForRelativeVersePath(FilePathUtils::GetDirectory(RelativeFilePath), Diagnostics); // Then gather settings and store the vmodule file path Module->_FilePath = Move(NormalizedFilePath); #else Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_InvalidModuleFile, CUTF8String("Found vmodule file `%s` which is not allowed.", *NormalizedFilePath) }); #endif } } else if (CSourceFileProject::IsPackageFile(FilePath) && FilePathUtils::NormalizePath(PackageFilePath) != NormalizedFilePath) { // Keep track of stray package files for sanity checking StrayPackageFilePaths.Add(NormalizedFilePath); } }; // 1) Gather named modules and files if (_FilePaths.IsSet()) { for (const CUTF8String& FilePath : *_FilePaths) { ProcessFile(FilePath); } } else { FileSystem->IterateDirectory(_DirPath.AsCString(), /*bRecursive =*/true, [&ProcessFile](const CUTF8StringView& FileName, const CUTF8StringView& Path, bool bIsDirectory) { if (!bIsDirectory) { ProcessFile(FilePathUtils::CombinePaths(Path, FileName)); } return true; // continue iteration }); } // 2) Report stray package files encountered for (const CUTF8String& StrayPackageFilePath : StrayPackageFilePaths) { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_IllegalSubPackage, CUTF8String("Found illegal additional vpackage `%s` underneath package `%s`.", *StrayPackageFilePath , *PackageFilePath) }); } // 3) Handle legacy named module files and adjust the module hierarchy accordingly #if VERSE_ALLOW_VMODULE_FILES auto TryRenameModule = [&Diagnostics](const TSRef& Module, CSourceFileModule* RenamedParent, bool bHasRenamedParent, auto& TryRenameModule) -> bool { bool bRenamed = false; // Does this module have a legacy name override? CUTF8StringView NameOverride = Module->GetNameFromFile(); if (NameOverride.IsFilled()) { // Yes rename, and make new parent for renamed submodules bRenamed = true; if (CSourceFileProject::IsValidModuleName(NameOverride)) { Module->_Name = NameOverride; RenamedParent = Module; bHasRenamedParent = true; } else { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_InvalidModuleName, CUTF8String("The path of the file `%s` contains disallowed characters that would lead to the invalid module name `%s`.", *Module->GetFilePath() , *CUTF8String(NameOverride)) }); } } // Recurse into submodules for (int32_t Index = Module->_Submodules.Num() - 1; Index >= 0; --Index) { TSRef Submodule = Module->_Submodules[Index].As(); if (TryRenameModule(Submodule, RenamedParent, bHasRenamedParent, TryRenameModule)) { // Submodule was renamed, reparent it to the nearest renamed parent Module->_Submodules.RemoveAt(Index); RenamedParent->_Submodules.Add(Submodule); } else if (bHasRenamedParent) { // Delete submodule and move its snippets to the nearest renamed parent Module->_Submodules.RemoveAt(Index); RenamedParent->_SourceSnippets.Append(Move(Submodule->_SourceSnippets)); ULANG_ENSUREF(Submodule->_Submodules.IsEmpty(), "Submodule must not have any submodules of its own left at this point."); } } return bRenamed; }; if (TryRenameModule(_RootModule.As(), _RootModule.As(), false, TryRenameModule)) { TSRef NewRootModule = TSRef::New("", FilePathUtils::AppendSlash(_RootModule.As()->GetDirPath())); NewRootModule->_Submodules.Add(_RootModule); _RootModule = NewRootModule; } #endif } //==================================================================================== // CSourceFileProject implementation //==================================================================================== bool FromJSON(const JSONValue& JSON, SPackageDesc* Value) { return FromJSON(JSON, "name", &Value->_Name, true) && FromJSON(JSON, "dirPath", &Value->_DirPath, true) && FromJSON(JSON, "filePaths", &Value->_FilePaths, false) && FromJSON(JSON, "settings", &Value->_Settings, true); } bool FromJSON(const JSONValue& JSON, SPackageRef* Value) { // Set optional values prior to read Value->_ReadOnly = false; Value->_Build = true; // There has to be one of path or desc bool HavePath = FromJSON(JSON, "path", &Value->_FilePath, false); return FromJSON(JSON, "desc", &Value->_Desc, !HavePath) && FromJSON(JSON, "readOnly", &Value->_ReadOnly, false) && FromJSON(JSON, "build", &Value->_Build, false); } bool FromJSON(const JSONValue& JSON, SProjectDesc* Value) { return FromJSON(JSON, "packages", &Value->_Packages, true); } CSourceFileProject::CSourceFileProject(const CUTF8String& ProjectFilePath, const TSRef& FileSystem, const TSRef& Diagnostics) : CSourceProject(FilePathUtils::GetNameFromFileOrDir(ProjectFilePath)) , _FilePath(ProjectFilePath) , _FileSystem(FileSystem) { // Parse project file and load packages specified in it CUTF8StringBuilder ProjectFileContents; const bool bReadSuccess = _FileSystem->FileRead(ProjectFilePath.AsCString(), [&ProjectFileContents](size_t ByteSize) { return (void*)ProjectFileContents.AppendBuffer(ByteSize); }); if (bReadSuccess) { // Set up RapidJSON document JSONAllocator Allocator; JSONMemoryPoolAllocator MemoryPoolAllocator(RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY, &Allocator); const size_t StackCapacity = 1024; JSONDocument ProjectDocument(&MemoryPoolAllocator, StackCapacity, &Allocator); // Parse project file into document SProjectDesc ProjectDesc; ProjectDocument.Parse(ProjectFileContents.AsCString(), ProjectFileContents.ByteLen()); if (!ProjectDocument.HasParseError() && FromJSON(ProjectDocument, &ProjectDesc)) { for (const SPackageRef& PackageRef : ProjectDesc._Packages) { // Add packages to build and skip packages not to build. [Could alternatively pass along `_Build` setting.] if (PackageRef._Build) { if (PackageRef._FilePath) { CUTF8String PackageFilePath = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(*PackageRef._FilePath, FilePathUtils::GetDirectory(ProjectFilePath))); TSRef NewPackage = TSRef::New(PackageFilePath, _FileSystem, Diagnostics); _Packages.Add({ NewPackage, PackageRef._ReadOnly }); } else if (ULANG_ENSUREF(PackageRef._Desc, "FromJSON must ensure that there is either a file path or a descriptor.")) { SPackageDesc FullDesc = *PackageRef._Desc; FullDesc._DirPath = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(FullDesc._DirPath, FilePathUtils::GetDirectory(ProjectFilePath))); if (!_FileSystem.Get()->DoesDirectoryExist(FullDesc._DirPath.AsCString())) { continue; } if (FullDesc._Settings._VniDestDir) { FullDesc._Settings._VniDestDir = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(*FullDesc._Settings._VniDestDir, FilePathUtils::GetDirectory(ProjectFilePath))); } TSRef NewPackage = TSRef::New(FullDesc, _FileSystem, Diagnostics); _Packages.Add({ NewPackage, PackageRef._ReadOnly }); } } } } else { Diagnostics->AppendGlitch({ EDiagnostic::ErrSyntax_MalformedProjectFile, CUTF8String("Cannot parse contents of project file `%s`.", *ProjectFilePath) }); } } else { Diagnostics->AppendGlitch({ EDiagnostic::ErrSystem_CannotReadText, CUTF8String("Unable to read project file `%s`.", *ProjectFilePath) }); } } CSourceFileProject::CSourceFileProject( const CUTF8String& Name, const TSRef& FileSystem, const TArray& Packages, const TSRef& Diagnostics) : CSourceProject(Name) , _FileSystem(FileSystem) { if (Packages.IsEmpty()) { Diagnostics->AppendGlitch({ EDiagnostic::WarnProject_EmptyProject }); } else { // Assemble project packages for (const SPackageDesc& Package : Packages) { TSRef NewPackage = TSRef::New(Package, _FileSystem, Diagnostics); const bool bIsReadOnly = false; _Packages.Add({ NewPackage, bIsReadOnly }); } } } bool CSourceFileProject::WriteProjectFile(const CUTF8String& ProjectFilePath, const TSRef& Diagnostics) { CSourceProjectWriter Writer(_FileSystem, Diagnostics); return Writer.WriteProjectFile(CSourceProjectWriter::GetProjectDesc(*this), ProjectFilePath); } bool CSourceFileProject::WriteVSCodeWorkspaceFile(const CUTF8String& WorkspaceFilePath, const CUTF8String& ProjectFilePath, const TSRef& Diagnostics) { CSourceProjectWriter Writer(_FileSystem, Diagnostics); return Writer.WriteVSCodeWorkspaceFile(CSourceProjectWriter::GetWorkspaceDesc(*this, ProjectFilePath), WorkspaceFilePath); } TOptional> CSourceFileProject::FindSnippetByFilePath(const CUTF8StringView& FilePath) const { for (const SPackage& Package : _Packages) { TOptional> Snippet = Package._Package.As()->FindSnippetByFilePath(FilePath); if (Snippet) { return Snippet; } } return EResult::Unspecified; } TOptional> CSourceFileProject::AddSnippet(const CUTF8StringView& FilePath) { for (const SPackage& Package : _Packages) { TOptional> Snippet = Package._Package.As()->AddSnippet(FilePath); if (Snippet) { return Snippet; } } return EResult::Error; } bool CSourceFileProject::RemoveSnippet(const CUTF8StringView& FilePath) { for (const SPackage& Package : _Packages) { if (Package._Package.As()->RemoveSnippet(FilePath)) { return true; } } return false; } bool CSourceFileProject::IsSnippetFile(const CUTF8StringView& FilePath) { return FilePath.SubViewEnd(SnippetExt.ByteLen()) == SnippetExt; } bool CSourceFileProject::IsModuleFile(const CUTF8StringView& FilePath) { return FilePath.SubViewEnd(ModuleExt.ByteLen()) == ModuleExt; } bool CSourceFileProject::IsPackageFile(const CUTF8StringView& FilePath) { return FilePath.SubViewEnd(PackageExt.ByteLen()) == PackageExt; } bool CSourceFileProject::IsProjectFile(const CUTF8StringView& FilePath) { return FilePath.SubViewEnd(ProjectExt.ByteLen()) == ProjectExt; } bool CSourceFileProject::IsValidModuleName(const CUTF8StringView& ModuleName) { const UTF8Char FirstCh = ModuleName.FirstByte(); if (!CUnicode::IsAlphaASCII(FirstCh) && FirstCh != '_') { return false; } for (const UTF8Char* Ch = ModuleName._Begin; Ch < ModuleName._End; ++Ch) { if (!CUnicode::IsAlphaASCII(*Ch) && !CUnicode::IsDigitASCII(*Ch) && *Ch != '_') { return false; } } return true; } bool CSourceFileProject::IsValidSnippetFileName(const CUTF8StringView& FileName) { const char* InvalidCharacters = CSourceFileSnippet::_InvalidSnippetCharacters; for (char Ch = *InvalidCharacters; Ch != '\0'; Ch = *(++InvalidCharacters)) { if (FileName.Contains(Ch)) { return false; } } if (FileName.EndsWith(SnippetExt)) { return true; } return false; } } // namespace uLang