diff --git a/.gitignore b/.gitignore index 170873f..2f2f9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ *.user *.userosscache *.sln.docstates +AutoAddUnityEngineAnalyzer/obj/ +AutoAddUnityEngineAnalyzer/Library/ +AutoAddUnityEngineAnalyzer/Temp/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -257,3 +260,6 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +.DS_Store +*.csproj +*.sln diff --git a/AutoAddUnityEngineAnalyzer/Assets/Editor/AutoAddUnityEngineAnalyzer.cs b/AutoAddUnityEngineAnalyzer/Assets/Editor/AutoAddUnityEngineAnalyzer.cs new file mode 100644 index 0000000..656d7ad --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/Assets/Editor/AutoAddUnityEngineAnalyzer.cs @@ -0,0 +1,68 @@ + +using UnityEditor; +using UnityEngine; +using System.IO; +using System.Xml.Linq; +using System.Linq; + +namespace UnityEngineAnalyzer +{ + + public class AutoAddUnityEngineAnalyzer : AssetPostprocessor + { + /// + /// Put your UnityEngineAnalyzer.dll in your unity project, + /// and modify this path relative to your project. + /// + public const string UnityEngineAnalyzerPath = "Tools\\VisualStudio\\UnityEngineAnalyzer\\UnityEngineAnalyzer.dll"; + + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + TryAddUnityEngineAnalyzer(); + } + + + private static void TryAddUnityEngineAnalyzer() + { + string dataPath = Application.dataPath + "/../"; + + var csprojPaths = Directory.GetFiles(dataPath, "*.csproj"); + + foreach(var oneCsProjPath in csprojPaths) + { + if(!string.IsNullOrEmpty(oneCsProjPath)) + { + XDocument doc = XDocument.Load(oneCsProjPath); + var defaultNamespace = doc.Root.GetDefaultNamespace(); + + var unityEngineAnalyzer = doc.Descendants(defaultNamespace + "Analyzer"). + Where(x => x.Attribute("Include"). + Value.Contains("UnityEngineAnalyzer.dll")). + FirstOrDefault(); + + if(unityEngineAnalyzer == null) + { + Debug.Log("can not find UnityEngineAnalyzer in oneCsProjPath=" + oneCsProjPath); + + try + { + doc.Root. + Add( + new XElement(defaultNamespace + "ItemGroup", + new XElement(defaultNamespace + "Analyzer", + new XAttribute("Include", UnityEngineAnalyzerPath)))); + + doc.Save(oneCsProjPath); + Debug.Log("did add UnityEngineAnalyzer in oneCsProjPath=" + oneCsProjPath); + } + catch (System.Exception ex) + { + Debug.LogError("exception caught in adding UnityEngineAnalyzer in oneCsProjPath=" + oneCsProjPath + "\nexception=" + ex); + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/AudioManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..da61125 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/AudioManager.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 0 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/ClusterInputManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..e7886b2 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/DynamicsManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..1931946 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 3 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_EnablePCM: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorBuildSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..6dc24f7 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: [] diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..c0c814f --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/EditorSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_ExternalVersionControlSupport: Visible Meta Files + m_SerializationMode: 2 + m_DefaultBehaviorMode: 1 + m_SpritePackerMode: 4 + m_SpritePackerPaddingPower: 1 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd + m_ProjectGenerationRootNamespace: + m_UserGeneratedProjectSuffix: + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/GraphicsSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..74d7b53 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,61 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_LegacyDeferred: + m_Mode: 1 + m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_LightsUseLinearIntensity: 0 + m_LightsUseColorTemperature: 0 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/InputManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..17c8f53 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/InputManager.asset @@ -0,0 +1,295 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/NavMeshAreas.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..6dd520f --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,89 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + m_SettingNames: + - Humanoid diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/NetworkManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/NetworkManager.asset new file mode 100644 index 0000000..5dc6a83 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/NetworkManager.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!149 &1 +NetworkManager: + m_ObjectHideFlags: 0 + m_DebugLevel: 0 + m_Sendrate: 15 + m_AssetToPrefab: {} diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/Physics2DSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..e3b2d0b --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 3 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_ChangeStopsCallbacks: 0 + m_CallbacksOnDisable: 1 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..7581a9f --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,597 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + productGUID: f0b4bd3f2cfa848008759759325afe07 + AndroidProfiler: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: AutoAddUnityEngineAnalyzer + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_SplashScreenBackgroundLandscape: {fileID: 0} + m_SplashScreenBackgroundPortrait: {fileID: 0} + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 0 + m_MTRendering: 1 + m_MobileMTRendering: 0 + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + tizenShowActivityIndicatorOnLoading: -1 + iosAppInBackgroundBehavior: 0 + displayResolutionDialog: 1 + iosAllowHTTPDownload: 1 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + disableDepthAndStencilBuffers: 0 + defaultIsFullScreen: 1 + defaultIsNativeResolution: 1 + runInBackground: 0 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + submitAnalytics: 1 + usePlayerLog: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 0 + graphicsJobs: 0 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + graphicsJobMode: 0 + macFullscreenMode: 2 + d3d9FullscreenMode: 1 + d3d11FullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + n3dsDisableStereoscopicView: 0 + n3dsEnableSharedListOpt: 1 + n3dsEnableVSync: 0 + ignoreAlphaClear: 0 + xboxOneResolution: 0 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + videoMemoryForVertexBuffers: 0 + psp2PowerMode: 0 + psp2AcquireBGM: 1 + wiiUTVResolution: 0 + wiiUGamePadMSAA: 1 + wiiUSupportsNunchuk: 0 + wiiUSupportsClassicController: 0 + wiiUSupportsBalanceBoard: 0 + wiiUSupportsMotionPlus: 0 + wiiUSupportsProController: 0 + wiiUAllowScreenCapture: 1 + wiiUControllerCount: 0 + m_SupportedAspectRatios: + 4:3: 1 + 5:4: 1 + 16:10: 1 + 16:9: 1 + Others: 1 + bundleVersion: 1.0 + preloadedAssets: [] + metroInputSource: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 0 + xboxOneEnable7thCore: 0 + vrSettings: + cardboard: + depthFormat: 0 + enableTransitionView: 0 + daydream: + depthFormat: 0 + useSustainedPerformanceMode: 0 + hololens: + depthFormat: 1 + protectGraphicsMemory: 0 + useHDRDisplay: 0 + targetPixelDensity: 0 + resolutionScalingMode: 0 + applicationIdentifier: {} + buildNumber: {} + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 16 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + VertexChannelCompressionMask: + serializedVersion: 2 + m_Bits: 238 + iPhoneSdkVersion: 988 + iOSTargetOSVersionString: + tvOSSdkVersion: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + iPhoneSplashScreen: {fileID: 0} + iPhoneHighResSplashScreen: {fileID: 0} + iPhoneTallHighResSplashScreen: {fileID: 0} + iPhone47inSplashScreen: {fileID: 0} + iPhone55inPortraitSplashScreen: {fileID: 0} + iPhone55inLandscapeSplashScreen: {fileID: 0} + iPadPortraitSplashScreen: {fileID: 0} + iPadHighResPortraitSplashScreen: {fileID: 0} + iPadLandscapeSplashScreen: {fileID: 0} + iPadHighResLandscapeSplashScreen: {fileID: 0} + appleTVSplashScreen: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSLargeIconLayers: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageWideLayers: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 0 + metalAPIValidation: 1 + iOSRenderExtraFrameOnPause: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + appleEnableAutomaticSigning: 0 + AndroidTargetDevice: 0 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidTVCompatibility: 1 + AndroidIsGame: 1 + androidEnableBanner: 1 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + resolutionDialogBanner: {fileID: 0} + m_BuildTargetIcons: [] + m_BuildTargetBatching: [] + m_BuildTargetGraphicsAPIs: [] + m_BuildTargetVRSettings: [] + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + webPlayerTemplate: APPLICATION:Default + m_TemplateCustomTags: {} + wiiUTitleID: 0005000011000000 + wiiUGroupID: 00010000 + wiiUCommonSaveSize: 4096 + wiiUAccountSaveSize: 2048 + wiiUOlvAccessKey: 0 + wiiUTinCode: 0 + wiiUJoinGameId: 0 + wiiUJoinGameModeMask: 0000000000000000 + wiiUCommonBossSize: 0 + wiiUAccountBossSize: 0 + wiiUAddOnUniqueIDs: [] + wiiUMainThreadStackSize: 3072 + wiiULoaderThreadStackSize: 1024 + wiiUSystemHeapSize: 128 + wiiUTVStartupScreen: {fileID: 0} + wiiUGamePadStartupScreen: {fileID: 0} + wiiUDrcBufferDisabled: 0 + wiiUProfilerLibPath: + playModeTestRunnerEnabled: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchTouchScreenUsage: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchDataLossConfirmation: 0 + switchSupportedNpadStyles: 3 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchPlayerConnectionEnabled: 1 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 120 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 1 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + ps4Passcode: 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 0 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + monoEnv: + psp2Splashimage: {fileID: 0} + psp2NPTrophyPackPath: + psp2NPSupportGBMorGJP: 0 + psp2NPAgeRating: 12 + psp2NPTitleDatPath: + psp2NPCommsID: + psp2NPCommunicationsID: + psp2NPCommsPassphrase: + psp2NPCommsSig: + psp2ParamSfxPath: + psp2ManualPath: + psp2LiveAreaGatePath: + psp2LiveAreaBackroundPath: + psp2LiveAreaPath: + psp2LiveAreaTrialPath: + psp2PatchChangeInfoPath: + psp2PatchOriginalPackage: + psp2PackagePassword: WRK5RhRXdCdG5nG5azdNMK66MuCV6GXi + psp2KeystoneFile: + psp2MemoryExpansionMode: 0 + psp2DRMType: 0 + psp2StorageType: 0 + psp2MediaCapacity: 0 + psp2DLCConfigPath: + psp2ThumbnailPath: + psp2BackgroundPath: + psp2SoundPath: + psp2TrophyCommId: + psp2TrophyPackagePath: + psp2PackagedResourcesPath: + psp2SaveDataQuota: 10240 + psp2ParentalLevel: 1 + psp2ShortTitle: Not Set + psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF + psp2Category: 0 + psp2MasterVersion: 01.00 + psp2AppVersion: 01.00 + psp2TVBootMode: 0 + psp2EnterButtonAssignment: 2 + psp2TVDisableEmu: 0 + psp2AllowTwitterDialog: 1 + psp2Upgradable: 0 + psp2HealthWarning: 0 + psp2UseLibLocation: 0 + psp2InfoBarOnStartup: 0 + psp2InfoBarColor: 0 + psp2ScriptOptimizationLevel: 0 + psmSplashimage: {fileID: 0} + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + spritePackerPolicy: + webGLMemorySize: 256 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLDataCaching: 0 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLUseWasm: 0 + webGLCompressionFormat: 1 + scriptingDefineSymbols: {} + platformArchitecture: {} + scriptingBackend: {} + incrementalIl2cppBuild: {} + additionalIl2CppArgs: + scriptingRuntimeVersion: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: AutoAddUnityEngineAnalyzer + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: AutoAddUnityEngineAnalyzer + wsaImages: {} + metroTileShortName: + metroCommandLineArgsFile: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, + a: 1} + metroSplashScreenUseBackgroundColor: 0 + platformCapabilities: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + metroCompilationOverrides: 1 + tizenProductDescription: + tizenProductURL: + tizenSigningProfileName: + tizenGPSPermissions: 0 + tizenMicrophonePermissions: 0 + tizenDeploymentTarget: + tizenDeploymentTargetType: -1 + tizenMinOSVersion: 1 + n3dsUseExtSaveData: 0 + n3dsCompressStaticMem: 1 + n3dsExtSaveDataNumber: 0x12345 + n3dsStackSize: 131072 + n3dsTargetPlatform: 2 + n3dsRegion: 7 + n3dsMediaSize: 0 + n3dsLogoStyle: 3 + n3dsTitle: GameName + n3dsProductCode: + n3dsApplicationId: 0xFF3FF + stvDeviceAddress: + stvProductDescription: + stvProductAuthor: + stvProductAuthorEmail: + stvProductLink: + stvProductCategory: 0 + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnableGPUVariability: 0 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + xboxOneScriptCompiler: 0 + vrEditorSettings: + daydream: + daydreamIconForeground: {fileID: 0} + daydreamIconBackground: {fileID: 0} + cloudServicesEnabled: {} + facebookSdkVersion: 7.9.4 + apiCompatibilityLevel: 2 + cloudProjectId: + projectName: + organizationId: + cloudEnabled: 0 + enableNativePlatformBackendsForNewInputSystem: 0 + disableOldInputManagerSupport: 0 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectVersion.txt b/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..4b80f92 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/ProjectVersion.txt @@ -0,0 +1 @@ +m_EditorVersion: 2017.1.2f1 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/QualitySettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..86c047f --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/QualitySettings.asset @@ -0,0 +1,193 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 5 + m_QualitySettings: + - serializedVersion: 2 + name: Very Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 1 + textureQuality: 1 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.3 + maximumLODLevel: 0 + particleRaycastBudget: 4 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.4 + maximumLODLevel: 0 + particleRaycastBudget: 16 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Medium + pixelLightCount: 1 + shadows: 1 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 1 + lodBias: 0.7 + maximumLODLevel: 0 + particleRaycastBudget: 64 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: High + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Very High + pixelLightCount: 3 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 70 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1.5 + maximumLODLevel: 0 + particleRaycastBudget: 1024 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Ultra + pixelLightCount: 4 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 4 + shadowDistance: 150 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 2 + maximumLODLevel: 0 + particleRaycastBudget: 4096 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + m_PerPlatformDefaultQuality: + Android: 2 + Nintendo 3DS: 5 + Nintendo Switch: 5 + PS4: 5 + PSM: 5 + PSP2: 2 + Samsung TV: 2 + Standalone: 5 + Tizen: 2 + Web: 5 + WebGL: 3 + WiiU: 5 + Windows Store Apps: 5 + XboxOne: 5 + iPhone: 2 + tvOS: 2 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/TagManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..1c92a78 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/TagManager.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/TimeManager.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..558a017 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/TimeManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/AutoAddUnityEngineAnalyzer/ProjectSettings/UnityConnectSettings.asset b/AutoAddUnityEngineAnalyzer/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..1cc5485 --- /dev/null +++ b/AutoAddUnityEngineAnalyzer/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,34 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + m_Enabled: 0 + m_TestMode: 0 + m_TestEventUrl: + m_TestConfigUrl: + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes + m_Enabled: 0 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_TestEventUrl: + m_TestConfigUrl: + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_EnabledPlatforms: 4294967295 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/Documents/usage.png b/Documents/usage.png new file mode 100644 index 0000000..7cf1290 Binary files /dev/null and b/Documents/usage.png differ diff --git a/UnityEngineAnalyzer.CLI/AnalyzerReport.cs b/UnityEngineAnalyzer.CLI/AnalyzerReport.cs index 8dd2add..cbebc27 100644 --- a/UnityEngineAnalyzer.CLI/AnalyzerReport.cs +++ b/UnityEngineAnalyzer.CLI/AnalyzerReport.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using Microsoft.CodeAnalysis; using UnityEngineAnalyzer.CLI.Reporting; @@ -17,6 +16,11 @@ public void AddExporter(IAnalyzerExporter exporter) _exporters.Add(exporter); } + public int GetExporterCount() + { + return _exporters.Count; + } + public void AppendDiagnostics(IEnumerable diagnosticResults) { if (_exporters.Count == 0) @@ -26,19 +30,31 @@ public void AppendDiagnostics(IEnumerable diagnosticResults) foreach (var diagnostic in diagnosticResults) { - var locationSpan = diagnostic.Location.SourceSpan; - var lineSpan = diagnostic.Location.SourceTree.GetLineSpan(locationSpan); + var location = diagnostic.Location; + var lineNumber = 0; + var characterPosition = 0; + var fileName = string.Empty; + + if (location != Location.None) + { + var locationSpan = location.SourceSpan; + var lineSpan = location.SourceTree.GetLineSpan(locationSpan); + lineNumber = lineSpan.StartLinePosition.Line; + characterPosition = lineSpan.StartLinePosition.Character; + fileName = location.SourceTree?.FilePath; + } var diagnosticInfo = new DiagnosticInfo { Id = diagnostic.Id, Message = diagnostic.GetMessage(), - FileName = diagnostic.Location.SourceTree.FilePath, - LineNumber = lineSpan.StartLinePosition.Line, - Severity = (DiagnosticInfoSeverity)diagnostic.Severity + FileName = fileName, + LineNumber = lineNumber, + CharacterPosition = characterPosition, + Severity = (DiagnosticInfo.DiagnosticInfoSeverity)diagnostic.Severity, + VersionSpan = DiagnosticDescriptors.GetVersion(diagnostic.Descriptor) }; - foreach (var exporter in _exporters) { exporter.AppendDiagnostic(diagnosticInfo); @@ -50,15 +66,23 @@ public void FinalizeReport(TimeSpan duration) { foreach (var exporter in _exporters) { - exporter.Finish(duration); + exporter.FinalizeExporter(duration); + } + } + + public void InitializeReport(Options options) + { + foreach (var exporter in _exporters) + { + exporter.InitializeExporter(options); } } - public void InitializeReport(FileInfo projectFile) + public void NotifyException(Exception exception) { foreach (var exporter in _exporters) { - exporter.InitializeExporter(projectFile); + exporter.NotifyException(exception); } } } diff --git a/UnityEngineAnalyzer.CLI/ConfigurationFileGenerator.cs b/UnityEngineAnalyzer.CLI/ConfigurationFileGenerator.cs new file mode 100644 index 0000000..8f0906d --- /dev/null +++ b/UnityEngineAnalyzer.CLI/ConfigurationFileGenerator.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; +using UnityEngineAnalyzer.ForEachInUpdate; + +namespace UnityEngineAnalyzer.CLI +{ + //NOTE: This kind of configuration file should be created after build + class ConfigurationFileGenerator + { + private const string configurationFileName = "analyzerConfiguration.json"; + + public void GenerateConfigurationFile() + { + var assembly = typeof(DoNotUseForEachInUpdate).Assembly; + var allTypes = assembly.DefinedTypes; + + var rootJson = new JObject(); + + foreach (var typeInfo in allTypes) + { + if (typeInfo.BaseType == typeof(DiagnosticAnalyzer)) + { + rootJson.Add(new JProperty(typeInfo.Name, true)); //TODO SupportedDiagnostics.IsEnabledByDefault; + } + } + + using (StreamWriter sw = File.CreateText("./" + configurationFileName)) + { + using (JsonTextWriter writer = new JsonTextWriter(sw)) + { + writer.Formatting = Formatting.Indented; + rootJson.WriteTo(writer); + } + } + } + } +} diff --git a/UnityEngineAnalyzer.CLI/Options.cs b/UnityEngineAnalyzer.CLI/Options.cs new file mode 100644 index 0000000..5892559 --- /dev/null +++ b/UnityEngineAnalyzer.CLI/Options.cs @@ -0,0 +1,24 @@ +using CommandLine; +using System.Collections.Generic; +using static UnityEngineAnalyzer.CLI.Reporting.DiagnosticInfo; + +namespace UnityEngineAnalyzer.CLI +{ + public class Options + { + [ValueOption(0)] + public string ProjectFile { get; set; } + + [Option('e', "exporter", HelpText = "Exporters to be used.")] + public IEnumerable Exporters { get; set; } + + [Option('c', "configuration", HelpText = "Custom json configuration to be used.")] + public string ConfigurationFile { get; set; } + + [Option('s', "severity", DefaultValue = DiagnosticInfoSeverity.Warning, HelpText = "Minimal severity to be reported.")] + public DiagnosticInfoSeverity MinimalSeverity { get; set; } + + [Option('v', "version", DefaultValue = UnityVersion.NONE, HelpText = "Check against spesific Unity version.")] + public UnityVersion Version { get; set; } + } +} \ No newline at end of file diff --git a/UnityEngineAnalyzer.CLI/Program.cs b/UnityEngineAnalyzer.CLI/Program.cs index 8c04e7a..9cee5a2 100644 --- a/UnityEngineAnalyzer.CLI/Program.cs +++ b/UnityEngineAnalyzer.CLI/Program.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using System.Threading.Tasks; using UnityEngineAnalyzer.CLI.Reporting; @@ -9,33 +8,71 @@ namespace UnityEngineAnalyzer.CLI { public class Program { + private static readonly Dictionary AvailableExporters = new Dictionary(); + + static Program() + { + AvailableExporters.Add(nameof(JsonAnalyzerExporter), typeof(JsonAnalyzerExporter)); + AvailableExporters.Add(nameof(StandardOutputAnalyzerReporter), typeof(StandardOutputAnalyzerReporter)); + AvailableExporters.Add(nameof(ConsoleAnalyzerExporter), typeof(ConsoleAnalyzerExporter)); + } + public static void Main(string[] args) { try { - if (args.Length <= 0) + var options = new Options(); + var isValid = CommandLine.Parser.Default.ParseArgumentsStrict(args, options); + + if (isValid == false || options.ProjectFile == null) { return; } + var unityVersionResolver = new UnityVersionResolver(); + options.Version = unityVersionResolver.ResolveVersion(options); + var startTime = DateTime.Now; - var fileName = args[0]; + var fileName = options.ProjectFile; var fileInfo = new FileInfo(fileName); //NOTE: This could be configurable via the CLI at some point var report = new AnalyzerReport(); - report.AddExporter(new ConsoleAnalyzerExporter()); - report.AddExporter(new JsonAnalyzerExporter()); + if (options.Exporters != null) + { + foreach (var exporter in options.Exporters) + { + if (AvailableExporters.ContainsKey(exporter)) + { + var exporterInstance = Activator.CreateInstance(AvailableExporters[exporter]); + report.AddExporter(exporterInstance as IAnalyzerExporter); + } + } + } - report.InitializeReport(fileInfo); + if (report.GetExporterCount() == 0) + { + //It's generally a good idea to make sure that the Console Exporter is last since it is interactive + report.AddExporter(new JsonAnalyzerExporter(options)); + report.AddExporter(new ConsoleAnalyzerExporter(options)); + } + + report.InitializeReport(options); var tasks = new List(); if (fileInfo.Exists) { + FileInfo configFileInfo = null; + + if (options.ConfigurationFile != null) + { + configFileInfo = new FileInfo(options.ConfigurationFile); + } + var solutionAnalyzer = new SolutionAnalyzer(); - var analyzeTask = solutionAnalyzer.LoadAnadAnalyzeProject(fileInfo, report); + var analyzeTask = solutionAnalyzer.LoadAndAnalyzeProjectAsync(fileInfo, configFileInfo, report); tasks.Add(analyzeTask); } @@ -46,8 +83,6 @@ public static void Main(string[] args) report.FinalizeReport(duration); - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); } catch (Exception generalException) { @@ -55,11 +90,9 @@ public static void Main(string[] args) Console.WriteLine("There was an exception running the analysis"); Console.WriteLine(generalException.ToString()); } - - - } + //TODO SET TO OWN CLASS } } diff --git a/UnityEngineAnalyzer.CLI/Reporting/AnalyzerExporter.cs b/UnityEngineAnalyzer.CLI/Reporting/AnalyzerExporter.cs new file mode 100644 index 0000000..9056dfc --- /dev/null +++ b/UnityEngineAnalyzer.CLI/Reporting/AnalyzerExporter.cs @@ -0,0 +1,34 @@ +using System; + +namespace UnityEngineAnalyzer.CLI.Reporting +{ + public abstract class AnalyzerExporter : IAnalyzerExporter + { + private readonly Options options; + + public AnalyzerExporter(Options options) + { + this.options = options; + } + + public bool IsAnalyzerRelevant(DiagnosticInfo diagnosticInfo) + { + if (options.MinimalSeverity > diagnosticInfo.Severity) + { + return false; + } + + if (options.Version < diagnosticInfo.VersionSpan.First || options.Version > diagnosticInfo.VersionSpan.Last) + { + return false; + } + + return true; + } + + public abstract void AppendDiagnostic(DiagnosticInfo diagnosticInfo); + public abstract void FinalizeExporter(TimeSpan duration); + public abstract void InitializeExporter(Options options); + public abstract void NotifyException(Exception exception); + } +} diff --git a/UnityEngineAnalyzer.CLI/Reporting/ConsoleAnalyzerExporter.cs b/UnityEngineAnalyzer.CLI/Reporting/ConsoleAnalyzerExporter.cs index bb4809a..7af1c76 100644 --- a/UnityEngineAnalyzer.CLI/Reporting/ConsoleAnalyzerExporter.cs +++ b/UnityEngineAnalyzer.CLI/Reporting/ConsoleAnalyzerExporter.cs @@ -1,64 +1,30 @@ using System; -using System.IO; namespace UnityEngineAnalyzer.CLI.Reporting { - public class ConsoleAnalyzerExporter : IAnalyzerExporter + public class ConsoleAnalyzerExporter : StandardOutputAnalyzerReporter { - private const string ConsoleSeparator = "\t"; - private const DiagnosticInfoSeverity MinimalSeverity = DiagnosticInfoSeverity.Warning; - - public void AppendDiagnostic(DiagnosticInfo diagnosticInfo) + public ConsoleAnalyzerExporter(Options options) : base(options) { - if (diagnosticInfo.Severity < MinimalSeverity) - { - return; - } - - Console.Write(diagnosticInfo.Id); - Console.Write(ConsoleSeparator); - - Console.ForegroundColor = ConsoleColorFromSeverity(diagnosticInfo.Severity); - Console.Write(diagnosticInfo.Severity.ToString()); - Console.Write(ConsoleSeparator); - - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write(diagnosticInfo.Message); - Console.ResetColor(); - Console.Write(ConsoleSeparator); - Console.WriteLine(@"{0}({1})",diagnosticInfo.FileName,diagnosticInfo.LineNumber); } - private ConsoleColor ConsoleColorFromSeverity(DiagnosticInfoSeverity severity) - { - switch (severity) - { - case DiagnosticInfoSeverity.Hidden: - return ConsoleColor.Gray; - case DiagnosticInfoSeverity.Info: - return ConsoleColor.Green; - case DiagnosticInfoSeverity.Warning: - return ConsoleColor.Yellow; - case DiagnosticInfoSeverity.Error: - return ConsoleColor.Red; - default: - return ConsoleColor.White; - } - } - - public void Finish(TimeSpan duration) + public override void FinalizeExporter(TimeSpan duration) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Console Export Finished ({0})", duration); Console.ResetColor(); + + Console.WriteLine("Press any key to exit"); + Console.ReadKey(); } - public void InitializeExporter(FileInfo projectFile) + public override void InitializeExporter(Options options) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Unity Syntax Analyzer"); Console.WriteLine(); - Console.WriteLine("Analyzing: {0}", projectFile.FullName); + Console.WriteLine("Analyzing: {0}", options.ProjectFile); + Console.WriteLine("With Unity version: " + Enum.GetName((typeof(UnityVersion)), options.Version)); Console.WriteLine(); Console.ResetColor(); } diff --git a/UnityEngineAnalyzer.CLI/Reporting/DiagnosticInfo.cs b/UnityEngineAnalyzer.CLI/Reporting/DiagnosticInfo.cs index e092be6..c13d113 100644 --- a/UnityEngineAnalyzer.CLI/Reporting/DiagnosticInfo.cs +++ b/UnityEngineAnalyzer.CLI/Reporting/DiagnosticInfo.cs @@ -1,19 +1,23 @@ namespace UnityEngineAnalyzer.CLI.Reporting { - public enum DiagnosticInfoSeverity - { - Hidden = 0, - Info = 1, - Warning = 2, - Error = 3 - } - public class DiagnosticInfo { + //TODO: Rename this to something like AnalysisResult + public string Id { get; set; } public string Message { get; set; } public string FileName { get; set; } public int LineNumber { get; set; } + public int CharacterPosition { get; set; } public DiagnosticInfoSeverity Severity { get; set; } + public UnityVersionSpan VersionSpan { get; set; } + + public enum DiagnosticInfoSeverity + { + Hidden = 0, + Info = 1, + Warning = 2, + Error = 3 + } } } \ No newline at end of file diff --git a/UnityEngineAnalyzer.CLI/Reporting/IAnalyzerExporter.cs b/UnityEngineAnalyzer.CLI/Reporting/IAnalyzerExporter.cs index 62b65ad..cdb9bc4 100644 --- a/UnityEngineAnalyzer.CLI/Reporting/IAnalyzerExporter.cs +++ b/UnityEngineAnalyzer.CLI/Reporting/IAnalyzerExporter.cs @@ -6,7 +6,8 @@ namespace UnityEngineAnalyzer.CLI.Reporting public interface IAnalyzerExporter { void AppendDiagnostic(DiagnosticInfo diagnosticInfo); - void Finish(TimeSpan duration); - void InitializeExporter(FileInfo projectFile); + void FinalizeExporter(TimeSpan duration); + void InitializeExporter(Options options); + void NotifyException(Exception exception); } } \ No newline at end of file diff --git a/UnityEngineAnalyzer.CLI/Reporting/JsonAnalyzerExporter.cs b/UnityEngineAnalyzer.CLI/Reporting/JsonAnalyzerExporter.cs index a0e7b6c..520ff15 100644 --- a/UnityEngineAnalyzer.CLI/Reporting/JsonAnalyzerExporter.cs +++ b/UnityEngineAnalyzer.CLI/Reporting/JsonAnalyzerExporter.cs @@ -1,45 +1,58 @@ using System; -using System.Diagnostics; +using System.Collections.Generic; using System.IO; using Newtonsoft.Json; namespace UnityEngineAnalyzer.CLI.Reporting { - public class JsonAnalyzerExporter : IAnalyzerExporter + public class JsonAnalyzerExporter : AnalyzerExporter { private const string JsonReportFileName = "report.json"; private const string HtmlReportFileName = "UnityReport.html"; - private const DiagnosticInfoSeverity MinimalSeverity = DiagnosticInfoSeverity.Warning; - private JsonTextWriter _jsonWriter; private readonly JsonSerializer _jsonSerializer = new JsonSerializer(); + private readonly List _exceptions = new List(); private string _destinationReportFile; + public JsonAnalyzerExporter(Options options) : base(options) + { + } - public void AppendDiagnostic(DiagnosticInfo diagnosticInfo) + public override void AppendDiagnostic(DiagnosticInfo diagnosticInfo) { - if (diagnosticInfo.Severity >= MinimalSeverity) + if (IsAnalyzerRelevant(diagnosticInfo)) { _jsonSerializer.Serialize(_jsonWriter, diagnosticInfo); } } - public void Finish(TimeSpan duration) + public override void FinalizeExporter(TimeSpan duration) { _jsonWriter.WriteEndArray(); + + _jsonWriter.WritePropertyName("Exceptions"); + _jsonWriter.WriteStartArray(); + + foreach (var exception in _exceptions) + { + _jsonSerializer.Serialize(_jsonWriter, exception); + } + _jsonWriter.WriteEndArray(); + _jsonWriter.WriteEndObject(); _jsonWriter.Close(); - + //Console.WriteLine(Process.GetCurrentProcess().StartInfo.WorkingDirectory); File.Copy(HtmlReportFileName, _destinationReportFile, true); //NOTE: This code might be temporary as it assumes that the CLI is being executed interactively //Process.Start(_destinationReportFile); } - public void InitializeExporter(FileInfo projectFile) + public override void InitializeExporter(Options options) { + var projectFile = new FileInfo(options.ProjectFile); if (!projectFile.Exists) { throw new ArgumentException("Project file does not exist"); @@ -68,5 +81,10 @@ public void InitializeExporter(FileInfo projectFile) _jsonSerializer.Formatting = Formatting.Indented; } + + public override void NotifyException(Exception exception) + { + _exceptions.Add(exception); + } } } \ No newline at end of file diff --git a/UnityEngineAnalyzer.CLI/Reporting/StandardOutputAnalyzerReporter.cs b/UnityEngineAnalyzer.CLI/Reporting/StandardOutputAnalyzerReporter.cs new file mode 100644 index 0000000..ee1dfb5 --- /dev/null +++ b/UnityEngineAnalyzer.CLI/Reporting/StandardOutputAnalyzerReporter.cs @@ -0,0 +1,77 @@ +using System; +using static UnityEngineAnalyzer.CLI.Reporting.DiagnosticInfo; + +namespace UnityEngineAnalyzer.CLI.Reporting +{ + public class StandardOutputAnalyzerReporter : AnalyzerExporter + { + protected const string ConsoleSeparator = "\t"; + protected const string FailurePrefix = "# "; + + public StandardOutputAnalyzerReporter(Options options) : base(options) + { + } + + public override void AppendDiagnostic(DiagnosticInfo diagnosticInfo) + { + if (IsAnalyzerRelevant(diagnosticInfo) == false) + { + return; + } + + Console.Write(diagnosticInfo.Id); + Console.Write(ConsoleSeparator); + + Console.ForegroundColor = ConsoleColorFromSeverity(diagnosticInfo.Severity); + Console.Write(diagnosticInfo.Severity.ToString()); + Console.Write(ConsoleSeparator); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write(diagnosticInfo.Message); + Console.ResetColor(); + Console.WriteLine(@"{0}{1}{0}{2},{3}", ConsoleSeparator,diagnosticInfo.FileName, diagnosticInfo.LineNumber, diagnosticInfo.CharacterPosition); + } + + private ConsoleColor ConsoleColorFromSeverity(DiagnosticInfoSeverity severity) + { + switch (severity) + { + case DiagnosticInfoSeverity.Hidden: + return ConsoleColor.Gray; + case DiagnosticInfoSeverity.Info: + return ConsoleColor.Green; + case DiagnosticInfoSeverity.Warning: + return ConsoleColor.Yellow; + case DiagnosticInfoSeverity.Error: + return ConsoleColor.Red; + default: + return ConsoleColor.White; + } + } + + public override void NotifyException(Exception exception) + { + Console.ForegroundColor = ConsoleColor.Red; + + var delimeters = new[] {"\r", "\n", Environment.NewLine }; + var exceptionLines = exception.ToString().Split(delimeters, StringSplitOptions.RemoveEmptyEntries); + + foreach (var exceptionLine in exceptionLines) + { + Console.WriteLine(FailurePrefix + exceptionLine); + } + + Console.WriteLine(FailurePrefix); + + Console.ResetColor(); + } + + public override void FinalizeExporter(TimeSpan duration) + { + } + + public override void InitializeExporter(Options options) + { + } + } +} diff --git a/UnityEngineAnalyzer.CLI/SolutionAnalyzer.cs b/UnityEngineAnalyzer.CLI/SolutionAnalyzer.cs index 724ee98..2b05582 100644 --- a/UnityEngineAnalyzer.CLI/SolutionAnalyzer.cs +++ b/UnityEngineAnalyzer.CLI/SolutionAnalyzer.cs @@ -7,23 +7,57 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.MSBuild; using UnityEngineAnalyzer.ForEachInUpdate; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; namespace UnityEngineAnalyzer.CLI { public class SolutionAnalyzer { - public async Task LoadAnadAnalyzeProject(FileInfo projectFile, AnalyzerReport report) //TODO: Add async suffix + public async Task LoadAndAnalyzeProjectAsync(FileInfo projectFile, FileInfo configFileInfo, AnalyzerReport report) { var workspace = MSBuildWorkspace.Create(); var project = await workspace.OpenProjectAsync(projectFile.FullName, CancellationToken.None); - var analyzers = this.GetAnalyzers(); + var analyzerDictionary = LoadConfigFile(configFileInfo); + var analyzers = this.GetAnalyzers(analyzerDictionary); await AnalyzeProject(project, analyzers, report); } - private ImmutableArray GetAnalyzers() + private Dictionary LoadConfigFile(FileInfo configFile) + { + if (configFile == null || configFile.Exists == false) + { + return null; + } + + Dictionary analyzerDictionary = new Dictionary(); + + var config = File.ReadAllText(configFile.FullName); //TODO: read async? + var jsonObject = JsonConvert.DeserializeObject(config); + + foreach (var item in jsonObject) + { + analyzerDictionary.Add(item.Key, (bool)item.Value); + } + + return analyzerDictionary; + } + + private bool IsAnalyzerAllowedInConfiguration(Dictionary analyzerDictionary, string analyzerName) + { + if (analyzerDictionary == null || analyzerDictionary.ContainsKey(analyzerName) == false) + { + return true; + } + + return analyzerDictionary[analyzerName]; + } + + private ImmutableArray GetAnalyzers(Dictionary analyzerDictionary) { var listBuilder = ImmutableArray.CreateBuilder(); @@ -34,23 +68,34 @@ private ImmutableArray GetAnalyzers() { if (type.BaseType == typeof(DiagnosticAnalyzer)) { - var instance = Activator.CreateInstance(type) as DiagnosticAnalyzer; - listBuilder.Add(instance); + if (IsAnalyzerAllowedInConfiguration(analyzerDictionary, type.Name)) + { + var instance = Activator.CreateInstance(type) as DiagnosticAnalyzer; + listBuilder.Add(instance); + } } } - var analyzers = listBuilder.ToImmutable(); return analyzers; } - private async Task AnalyzeProject(Project project, ImmutableArray analyzers, AnalyzerReport report) + private async Task AnalyzeProject(Project project, ImmutableArray analyzers, + AnalyzerReport report) { - var compilation = await project.GetCompilationAsync(); + try + { + var compilation = await project.GetCompilationAsync(); + + var diagnosticResults = await compilation.WithAnalyzers(analyzers).GetAnalyzerDiagnosticsAsync(); - var diagnosticResults = await compilation.WithAnalyzers(analyzers).GetAnalyzerDiagnosticsAsync(); + report.AppendDiagnostics(diagnosticResults); + } + catch (Exception exception) + { + report.NotifyException(exception); + } - report.AppendDiagnostics(diagnosticResults); } } diff --git a/UnityEngineAnalyzer.CLI/UnityEngineAnalyzer.CLI.csproj b/UnityEngineAnalyzer.CLI/UnityEngineAnalyzer.CLI.csproj index 1e6c031..a1ef17c 100644 --- a/UnityEngineAnalyzer.CLI/UnityEngineAnalyzer.CLI.csproj +++ b/UnityEngineAnalyzer.CLI/UnityEngineAnalyzer.CLI.csproj @@ -33,6 +33,9 @@ 4 + + ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll + ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll True @@ -101,16 +104,24 @@ + + ..\..\..\..\..\..\..\Program Files\Unity2017.1.3p2\Editor\Data\Managed\UnityEngine.dll + + + + + + diff --git a/UnityEngineAnalyzer.CLI/UnityVersionResolver.cs b/UnityEngineAnalyzer.CLI/UnityVersionResolver.cs new file mode 100644 index 0000000..cb53e73 --- /dev/null +++ b/UnityEngineAnalyzer.CLI/UnityVersionResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace UnityEngineAnalyzer.CLI +{ + //NOTE: This class would benefit from UnitTests specially: TryParseUnityVersion + internal class UnityVersionResolver + { + private const UnityVersion DEFAULT_UNITY_VERSION = UnityVersion.LATEST; + + public UnityVersion ResolveVersion(Options options) + { + if (options.Version != UnityVersion.NONE) + { + return options.Version; + } + + //THIS ONLY WORKS ON UNITY >= 5, before that ProjectVersion.txt did not exists + if (ResolveProjectVersionFilePath(options) != null) + { + var projectVersionString = File.ReadAllText(ResolveProjectVersionFilePath(options)); + return TryParseUnityVersion(projectVersionString); + } + + return DEFAULT_UNITY_VERSION; + } + + private string ResolveProjectVersionFilePath(Options options) + { + var projectPath = new FileInfo(options.ProjectFile).Directory; + var path = Path.Combine(projectPath.FullName, "ProjectSettings", "ProjectVersion.txt"); + var projectVersionFile = new FileInfo(path); + + if (projectVersionFile.Exists) + { + return projectVersionFile.FullName; + } + + return null; + } + + private UnityVersion TryParseUnityVersion(string version) + { + string editorText = "m_EditorVersion: "; + var match = Regex.Match(version, editorText + "[0-9.a-z]*"); + + string src = match.Value.Substring(editorText.Length); + src = src.Replace('.', '_'); + src = src.Substring(0, src.IndexOf('_') + 2); + + var unityVersions = Enum.GetValues(typeof(UnityVersion)).Cast(); + foreach (var unityVersion in unityVersions) + { + if (Enum.GetName(typeof(UnityVersion), unityVersion).Contains(src)) + { + return unityVersion; + } + } + + return DEFAULT_UNITY_VERSION; + } + } +} diff --git a/UnityEngineAnalyzer.CLI/analyzerConfiguration.json b/UnityEngineAnalyzer.CLI/analyzerConfiguration.json new file mode 100644 index 0000000..4f9e6ae --- /dev/null +++ b/UnityEngineAnalyzer.CLI/analyzerConfiguration.json @@ -0,0 +1,16 @@ +{ + "DoNotUseStringMethodsAnalyzer": true, + "InvokeFunctionMissingAnalyzer": true, + "UseNonAllocMethodsAnalyzer": true, + "DoNotUseOnGUIAnalyzer": true, + "UnsealedDerivedClassAnalyzer": true, + "DoNotUseForEachInUpdate": true, + "DoNotUseFindMethodsInUpdateAnalyzer": true, + "EmptyMonoBehaviourMethodsAnalyzer": true, + "DoNotUseCoroutinesAnalyzer": true, + "UseCompareTagAnalyzer": true, + "DoNotUseReflectionEmitAnalyzer": true, + "DoNotUseRemotingAnalyzer": true, + "TypeGetTypeAnalyzer": true, + "DoNotUseStateNameAnalyzer": true +} \ No newline at end of file diff --git a/UnityEngineAnalyzer.CLI/packages.config b/UnityEngineAnalyzer.CLI/packages.config index 5c06964..ef2d310 100644 --- a/UnityEngineAnalyzer.CLI/packages.config +++ b/UnityEngineAnalyzer.CLI/packages.config @@ -1,5 +1,6 @@  + diff --git a/UnityEngineAnalyzer.dll b/UnityEngineAnalyzer.dll new file mode 100644 index 0000000..4825925 Binary files /dev/null and b/UnityEngineAnalyzer.dll differ diff --git a/UnityEngineAnalyzer.sln b/UnityEngineAnalyzer.sln index c4bc4fa..f4e1cdf 100644 --- a/UnityEngineAnalyzer.sln +++ b/UnityEngineAnalyzer.sln @@ -1,14 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngineAnalyzer", "UnityEngineAnalyzer\UnityEngineAnalyzer\UnityEngineAnalyzer.csproj", "{BFB2BB34-FED8-48CC-9B83-A6E38BA5666C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngineAnalyzer.Test", "UnityEngineAnalyzer\UnityEngineAnalyzer.Test\UnityEngineAnalyzer.Test.csproj", "{76CAC24F-3E52-43A1-A696-6848EB9C1E7B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngineAnalyzer.Vsix", "UnityEngineAnalyzer\UnityEngineAnalyzer.Vsix\UnityEngineAnalyzer.Vsix.csproj", "{23A41489-857C-4FC7-9B96-2F20D8EE380B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngineAnalyzer.CLI", "UnityEngineAnalyzer.CLI\UnityEngineAnalyzer.CLI.csproj", "{9C2B02D9-9DCF-4617-AEF5-C00B2A332B83}" EndProject Global @@ -25,10 +23,6 @@ Global {76CAC24F-3E52-43A1-A696-6848EB9C1E7B}.Debug|Any CPU.Build.0 = Debug|Any CPU {76CAC24F-3E52-43A1-A696-6848EB9C1E7B}.Release|Any CPU.ActiveCfg = Release|Any CPU {76CAC24F-3E52-43A1-A696-6848EB9C1E7B}.Release|Any CPU.Build.0 = Release|Any CPU - {23A41489-857C-4FC7-9B96-2F20D8EE380B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23A41489-857C-4FC7-9B96-2F20D8EE380B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23A41489-857C-4FC7-9B96-2F20D8EE380B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23A41489-857C-4FC7-9B96-2F20D8EE380B}.Release|Any CPU.Build.0 = Release|Any CPU {9C2B02D9-9DCF-4617-AEF5-C00B2A332B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9C2B02D9-9DCF-4617-AEF5-C00B2A332B83}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C2B02D9-9DCF-4617-AEF5-C00B2A332B83}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -37,4 +31,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AED667A6-0E4F-4396-A379-CE20111B4D2C} + EndGlobalSection EndGlobal diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs new file mode 100644 index 0000000..b245e8f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Animator; + +namespace UnityEngineAnalyzer.Test.Animator +{ + + [TestFixture] + sealed class DoNotSetAnimatorParameterWithNameAnalyzerTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseStateNameAnalyzer(); + + [Test] + public void AnimatorSetFloatStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetFloat(""Run"", 1.2f)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + + [Test] + public void AnimatorSetIntStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetInteger(""Walk"", 1)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + + [Test] + public void AnimatorSetBoolStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetBool(""Fly"", true)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/CameraMainAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/CameraMainAnalyzerTests.cs new file mode 100644 index 0000000..7fba584 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/CameraMainAnalyzerTests.cs @@ -0,0 +1,107 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Camera; + +namespace UnityEngineAnalyzer.Test.Camera +{ + [TestFixture] + sealed class CameraMainAnalyzerTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new CameraMainAnalyzer(); + + [Test] + public void CameraMainShouldRaiseWarningOnMemberExpression() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + var orthographicSize = [|Camera.main.orthographicSize|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.CameraMainIsSlow); + } + + [Test] + public void CameraMainShouldRaiseWarningOnMethod() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + var size = [|Camera.main.ScreenPointToRay|](new Vector3(200, 200, 0)); + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.CameraMainIsSlow); + } + + [Test] + public void CameraMainShouldThrowWarningFromCalledMethods() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + Call(); + + //Camera.main.transform.position = Vector3.one; + } + + public void Call() + { + var size = [|Camera.main.ScreenPointToRay|](new Vector3(200, 200, 0)); + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.CameraMainIsSlow); + } + + [Test] + public void CameraMainShouldThrowWarningOnlyInHotPath() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Start() + { + var size = [|Camera.main.ScreenPointToRay|](new Vector3(200, 200, 0)); + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + NoDiagnostic(document, DiagnosticIDs.CameraMainIsSlow); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/DoNotUseCameraMainInUpdateAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/DoNotUseCameraMainInUpdateAnalyzerTests.cs new file mode 100644 index 0000000..b5bdbbf --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Camera/DoNotUseCameraMainInUpdateAnalyzerTests.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.FindMethodsInUpdate; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.FindMethodsInUpdate +{ + [TestFixture] + sealed class DoNotUseCameraMainInUpdateAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseCameraMainInUpdateAnalyzer(); + + [Test] + public void CameraMainInUpdate() + { + var code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + Camera main = [|Camera.main|]; + + //var result = GameObject.Find(""param""); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseCameraMainInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void CameraMainInUpdateRecursive() + { + var code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + [|MyMethod()|]; + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + Camera main = Camera.main; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseCameraMainInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Delegates/ShouldCacheDelegateAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Delegates/ShouldCacheDelegateAnalyzerTests.cs new file mode 100644 index 0000000..46be844 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Delegates/ShouldCacheDelegateAnalyzerTests.cs @@ -0,0 +1,800 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Delegates; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.Delegates +{ + [TestFixture] + sealed class ShouldCacheDelegatesAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new ShouldCacheDelegateAnalyzer(); + + [Test] + public void DidNotCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public EventHandler e; + void Update() + { + e += [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void DidNotCacheDelegateInAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public EventHandler e; + void Awake() + { + e += [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void DidNotCacheDelegate2() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public EventHandler e; + void Update() + { + e += this.[|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void DidNotCacheDelegate2InAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public EventHandler e; + void Awake() + { + e += this.[|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void EventDidNotCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + e += [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void EventDidNotCacheDelegateInAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Awake() + { + e += [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void EventDidNotCacheDelegate2() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + e -= [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void EventDidNotCacheDelegate2InAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Awake() + { + e -= [|OnCallBack|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void EventDidCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + private EventHandler m_cachedDelegate = OnCallBack; + + void Intialize() + { + m_cachedDelegate = OnCallBack; + } + + void Update() + { + e += [|m_cachedDelegate|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void FunctionDidNotCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + CallDelegate([|OnCallBack|]); + } + + private void CallDelegate(EventHandler handler) + { + + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void FunctionDidNotCacheDelegateInAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Awake() + { + CallDelegate([|OnCallBack|]); + } + + private void CallDelegate(EventHandler handler) + { + + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void FunctionDidCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + private EventHandler m_cachedDelegate; + + void Intialize() + { + m_cachedDelegate = OnCallBack; + } + + void Update() + { + CallDelegate([|m_cachedDelegate|]); + } + + private void CallDelegate(EventHandler handler) + { + + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void FunctionIsNotDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + Call([|ReturnInt()|]); + } + + private void Call(int intValue) + { + + } + + private int ReturnInt() + { + return 0; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void FunctionIsNotDelegateInAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Awake() + { + Call([|ReturnInt()|]); + } + + private void Call(int intValue) + { + + } + + private int ReturnInt() + { + return 0; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void FunctionIsNotDelegate2() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + Call(this.[|ReturnInt()|]); + } + + private void Call(int intValue) + { + + } + + private int ReturnInt() + { + return 0; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void FunctionIsNotDelegate3() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public string s; + private EventHandler m_cachedDelegate = OnCallBack; + + void Intialize() + { + m_cachedDelegate = OnCallBack; + } + + void Update() + { + s += [|ToString()|]; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void FunctionIsNotDelegateAndDidNotCacheDelegate() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + Call(ReturnInt(), [|OnCallBack|]); + } + + private void Call(int intValue, EventHandler h) + { + + } + + private int ReturnInt() + { + return 0; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void FunctionIsNotDelegateAndDidNotCacheDelegateInAwake() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Awake() + { + Call(ReturnInt(), [|OnCallBack|]); + } + + private void Call(int intValue, EventHandler h) + { + + } + + private int ReturnInt() + { + return 0; + } + + private void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void FunctionIsNotDelegateAndDidNotCacheDelegate_IgnoreStaticMethod() + { + var code = @" + +using System; +using UnityEngine; + +class C : MonoBehaviour +{ + public event EventHandler e; + void Update() + { + Call(ReturnInt(), [|OnCallBack|]); + } + + private void Call(int intValue, EventHandler h) + { + + } + + private int ReturnInt() + { + return 0; + } + + private static void OnCallBack(object sender, EventArgs e) + { + throw new NotImplementedException(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.ShouldCacheDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixTests.cs new file mode 100644 index 0000000..6fd4ec4 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Text; +using NUnit.Core; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.EmptyMonoBehaviourMethods; + +namespace UnityEngineAnalyzer.Test.EmptyMonoBehaviourMethods +{ + [TestFixture] + sealed class EmptyMonoBehaviourMethodsCodeFixTests:CodeFixTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override CodeFixProvider CreateProvider()=> new EmptyMonoBehaviourMethodsCodeFixer(); + + [Test] + public void RemoveEmptyMonoMethods() + { + const string rawStr = @" +using UnityEngine + +class C : MonoBehaviour +{ + [|void Update() { }|] +}"; + + const string expectStr = @" +using UnityEngine + +class C : MonoBehaviour +{ +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(rawStr, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + TestCodeFix(document, span, expectStr, DiagnosticDescriptors.EmptyMonoBehaviourMethod); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateAnalyzerTests.cs index aa72bff..4a27712 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateAnalyzerTests.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateAnalyzerTests.cs @@ -47,6 +47,42 @@ void Update() } } + + [Test] + public void GameObjectFindInUpdateRecursive() + { + var code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + [|MyMethod()|]; + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + GameObject.Find(""param""); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseFindMethodsInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] public void GameObjectFindInStart() { diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/ForEachInUpdate/DoNotUseForeachInUpdateAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/ForEachInUpdate/DoNotUseForeachInUpdateAnalyzerTests.cs index 24649e0..541f781 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/ForEachInUpdate/DoNotUseForeachInUpdateAnalyzerTests.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/ForEachInUpdate/DoNotUseForeachInUpdateAnalyzerTests.cs @@ -11,46 +11,46 @@ namespace UnityEngineAnalyzer.Test.ForEachInUpdate { - [TestFixture] - sealed class DoNotUseForeachInUpdateAnalyzerTests : AnalyzerTestFixture - { +[TestFixture] +sealed class DoNotUseForeachInUpdateAnalyzerTests : AnalyzerTestFixture +{ - protected override string LanguageName => LanguageNames.CSharp; - protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseForEachInUpdate(); + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseForEachInUpdate(); - [Test] - public void ForEachInUpdate() - { - var code = @" + [Test] + public void ForEachInUpdate() + { + var code = @" using UnityEngine; class C : MonoBehaviour { - void Update() - { - var colors = new[] {""red"", ""white"", ""blue""}; - var result = string.Empty; - [|foreach|] (var color in colors) - { - result += color; - } +void Update() +{ + var colors = new[] {""red"", ""white"", ""blue""}; + var result = string.Empty; + [|foreach|] (var color in colors) + { + result += color; } + } }"; - - Document document; - TextSpan span; - if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, - out document, out span)) - { - HasDiagnostic(document, span, DiagnosticIDs.DoNotUseForEachInUpdate); - } - else - { - Assert.Fail("Could not load unit test code"); - } + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseForEachInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); } } } +} \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotBoxWhenInvokeAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotBoxWhenInvokeAnalyzerTests.cs new file mode 100644 index 0000000..2bb8f8f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotBoxWhenInvokeAnalyzerTests.cs @@ -0,0 +1,262 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.GCAlloc; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.GCAlloc +{ + [TestFixture] + sealed class DoNotBoxWhenInvokeAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotBoxWhenInvokeAnalyzer(); + + [Test] + public void BoxWhenInvokeWithLiteral() + { + var code = @" + +class C +{ + private void Method(object p1, int p2) + { + } + + private void Caller() + { + Method([|234|], 2); + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void BoxWhenInvokeWithIdentifier() + { + var code = @" + +class C +{ + private void Method(object p1, int p2) + { + } + + private void Caller() + { + int arg = 234; + Method([|arg|], 2); + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void NoBoxWhenInvokeWithLiteral() + { + var code = @" + +class C +{ + private void Method(int p1, int p2) + { + } + + private void Caller() + { + Method([|234|], 2); + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void NoBoxWhenInvokeWithIdentifier() + { + var code = @" + +class C +{ + private void Method(int p1, int p2) + { + } + + private void Caller() + { + int arg = 234; + Method([|arg|], 2); + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void BoxIgnoredWhenConditionalInvokeWithLiteral() + { + var code = @" + +using System.Diagnostics; + +class C +{ + [Conditional(""SOME_CONDITION""), Conditional(""SOME_CONDITION2"")] + private void Method(object p1, int p2) + { + } + + private void Caller() + { + Method([|234|], 2); + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + // For params argument, shoule be some external process: + // if type of params array is value type: won't box; + // if type of params array is reference type: box. + + [Test] + public void NoBoxInParams() + { + var code = @" +class C +{ + private void Method(params int[] argus) + { + } + + private void Caller() + { + int arg = 234; + Method([|arg, 2|]); + } +} +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void BoxInParams() + { + var code = @" +class C +{ + private void Method(params C[] argus) + { + } + + private void Caller() + { + int arg = 234; + Method([|arg|], [|2|]); + } +} +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotBoxWhenInvoke); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotGCAllocInUpdateAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotGCAllocInUpdateAnalyzerTests.cs new file mode 100644 index 0000000..dd25ab1 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/GCAlloc/DoNotGCAllocInUpdateAnalyzerTests.cs @@ -0,0 +1,278 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.GCAlloc; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.GCAlloc +{ + [TestFixture] + sealed class DoNotGCAllocInUpdateAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotGCAllocInUpdateAnalyzer(); + + [Test] + public void GCAllocValueTypeInUpdate() + { + var code = @" +using UnityEngine; + +struct S {} + +class C : MonoBehaviour +{ + void Update() + { + S s = [|new S()|]; + + //var result = GameObject.Find(""param""); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void GCAllocInUpdate() + { + var code = @" +using UnityEngine; + +class B {} + +class C : MonoBehaviour +{ + void Update() + { + B b = [|new B()|]; + + //var result = GameObject.Find(""param""); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void GCAllocInBranchInUpdate() + { + var code = @" +using UnityEngine; + +class B {} + +class C : MonoBehaviour +{ + void Update() + { + B b; + if(b == null) + { + b = [|new B()|]; + }; + + //var result = GameObject.Find(""param""); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void GCAllocInUpdateRecursive() + { + var code = @" +using UnityEngine; + +class B {} + +class C : MonoBehaviour +{ + void Update() + { + [|MyMethod()|]; + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + B b = new B(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void GCAllocInUpdateRecursiveWithBranch() + { + var code = @" +using UnityEngine; + +class B {} + +class C : MonoBehaviour +{ + void Update() + { + if(true) + { + [|MyMethod()|]; + } + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + B b = new B(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void GCAllocInUpdateRecursiveWithBranch2() + { + var code = @" +using UnityEngine; + +class B {} + +class C : MonoBehaviour +{ + void Update() + { + [|MyMethod()|]; + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + if(true) + { + B b = new B(); + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void GCAllocValueTypeInUpdateRecursive() + { + var code = @" +using UnityEngine; + +struct S {} + +class C : MonoBehaviour +{ + void Update() + { + [|MyMethod()|]; + //var result = GameObject.Find(""param""); + } + + void MyMethod() + { + S s = new S(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNotGCAllocInUpdate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/DoNotUseEnumTypeParameterAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/DoNotUseEnumTypeParameterAnalyzerTests.cs new file mode 100644 index 0000000..4a64414 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/DoNotUseEnumTypeParameterAnalyzerTests.cs @@ -0,0 +1,132 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Generics; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.GCAlloc +{ + [TestFixture] + sealed class DoNotUseEnumTypeParameterAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseEnumTypeParameterAnalyzer(); + + [Test] + public void UseEnumTypeParameter() + { + var code = @" + +class C +{ + public enum MyEnum { One, Two } + public Dictionary<[|MyEnum|], int> map; +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNoUseEnumTypeParameter); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void UseEnumTypeParameter2() + { + var code = @" + +class C +{ + public enum MyEnum { One, Two } + public Dictionary> map; +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNoUseEnumTypeParameter); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void UseEnumTypeParameter3() + { + var code = @" + +class C +{ + public enum MyEnum { One, Two } + public Dictionary>> map; +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.DoNoUseEnumTypeParameter); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void UseNoEnumTypeParameter() + { + var code = @" + +class C +{ + public enum MyEnum { One, Two } + public Dictionary<[|int|], int> map; +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.DoNoUseEnumTypeParameter); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/EnumShouldManualSetMemberValueTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/EnumShouldManualSetMemberValueTests.cs new file mode 100644 index 0000000..a057255 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Generics/EnumShouldManualSetMemberValueTests.cs @@ -0,0 +1,75 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Generics; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.GCAlloc +{ + [TestFixture] + sealed class EnumShouldManualSetMemberValueTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new EnumShouldManualSetMemberValue(); + + [Test] + public void EnumWithoutManualMemberValue() + { + var code = @" + +enum E +{ + [|a|], + b = 2, +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.EnumShouldManualSetMemberValue); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void EnumWithManualMemberValue() + { + var code = @" + +enum E +{ + [|a|] = 1, + b = 2, +} +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.EnumShouldManualSetMemberValue); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassAnalyzerTests.cs new file mode 100644 index 0000000..f98832c --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassAnalyzerTests.cs @@ -0,0 +1,58 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.IL2CPP; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.GCAlloc +{ + [TestFixture] + sealed class UnsealedDerivedClassAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new UnsealedDerivedClassAnalyzer(); + + [Test] + public void UnsealedOverrideMethod() + { + var code = @" + +class C +{ + protected virtual void Method() + { + } +} + + +class D : C +{ + protected override void [|Method|]() + { + } +} + +"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, null, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.UnsealedDerivedClass); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassCodeFixTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassCodeFixTests.cs new file mode 100644 index 0000000..00f9b78 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/IL2CPP/UnsealedDerivedClassCodeFixTests.cs @@ -0,0 +1,63 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Text; +using NUnit.Core; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.IL2CPP; + +namespace UnityEngineAnalyzer.Test.IL2CPP +{ + [TestFixture] + sealed class UnsealedDerivedClassCodeFixTests:CodeFixTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override CodeFixProvider CreateProvider()=>new UnsealedDerivedClassCodeFixer(); + + [Test] + public void AddSealedModifier() + { + const string rawStr = @" +class C +{ + protected virtual void Method() + { + } +} + + +class D : C +{ + protected override void [|Method|]() + { + } +} + +"; + + const string expectStr = @" +class C +{ + protected virtual void Method() + { + } +} + + +class D : C +{ + protected sealed override void Method() + { + } +} + +"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(rawStr, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + TestCodeFix(document, span, expectStr, DiagnosticDescriptors.UnsealedDerivedClass); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/DuplicatedDelegateDetectionTestCases.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/DuplicatedDelegateDetectionTestCases.cs new file mode 100644 index 0000000..eefa77f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/DuplicatedDelegateDetectionTestCases.cs @@ -0,0 +1,190 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; + +namespace UnityEngineAnalyzer.Test.Language +{ + [TestFixture] + class DuplicatedDelegateDetectionTestCases:AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer()=>new DuplicatedDelegateDetection(); + + [Test] + public void DetectCustomedDelegateDeclaration() + { + var code = @" +class TestHost +{ + [|private delegate void TestDelegate(string argu1);|] +} +"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + // HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void DetectUDTDelegateDeclaration() + { + var code = @" +class TestHost +{ + [|private delegate TestHost TestDelegate(TestHost argu1);|] +} +"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + // HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void DetectUDTDelegateDeclarationWithOutKeyword() + { + var code = @" +class TestHost +{ + [|private delegate TestHost TestDelegate(TestHost argu1, out int arg2);|] +} +"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + // HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void DetectDelegateAsParam() + { + var code = @" +class TestHost +{ + [|private delegate string TestDelegate(string argu);|] + + private void TestMethod([|TestDelegate argu|]) + { + } +} +"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void NoDetectDelegateAsParam() + { + var code = @" +using System; + +class TestHost +{ + private void TestMethod([|Action argu|]) + { + } +} +"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void DetectDelegateAsVariable() + { + var code = @" +using System; + +class TestHost +{ + [|private delegate void Miaow(string str);|] + + private [|Miaow|] aa; +} +"; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out var document, out var span)) + { + HasDiagnostic(document, span, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void NoDetectDelegateAsVariable() + { + var code = @" +using System; + +class TestHost +{ + [|private Action aa;|] +} +"; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out var document, out _)) + { + NoDiagnostic(document, DiagnosticIDs.UseCommonDelegate); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/LambdaLocalVaribleTestCases.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/LambdaLocalVaribleTestCases.cs new file mode 100644 index 0000000..463db4f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/LambdaLocalVaribleTestCases.cs @@ -0,0 +1,92 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; + +namespace UnityEngineAnalyzer.Test.Language +{ + [TestFixture] + class LambdaLocalVaribleTestCases:AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer()=>new LambdaClosureAnalyzer(); + + [Test] + public void ExpressionWithConstructClosure() + { + var code = @" +using System; + +class TestHost +{ + private void QueryLambdaArgu(Action callback) + { + callback(); + } + + public void TestMethod() + { + int a; + QueryLambdaArgu(() => { + int b = 999; + int c = [|a|]; + [|a|].ToString(); + b.ToString(); + b.ToString(); + }); + } +}"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.LambdaUseLocalVariable); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void ExpressionWithoutConstructClosure() + { + var code = @" +using System; + +class TestHost +{ + private void QueryLambdaArgu(Action callback) + { + callback(); + } + + public void TestMethod() + { + int a; + QueryLambdaArgu([|() => { + int b = 999; + b.ToString(); + b.ToString(); + }|]); + } +}"; + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, + MetadataReferenceHelper.UsingUnityEngine, out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.LambdaUseLocalVariable); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructImplementAnalyzerTest.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructImplementAnalyzerTest.cs new file mode 100644 index 0000000..365921a --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructImplementAnalyzerTest.cs @@ -0,0 +1,101 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.Language +{ + [TestFixture] + sealed class StructImplementationAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new StructImplementAnalyzer(); + + [Test] + public void StructDoNotImplementIEquatable() + { + var code = @" +using UnityEngine; + +struct [|S|] +{ +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.StructShouldImplementIEquatable); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void StructDoImplementIEquatable() + { + var code = @" +using UnityEngine; +using System; + +struct [|S|] : IEquatable +{ + public bool Equals(S other) { return false; } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.StructShouldImplementIEquatable); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void StructDoNotImplementCorrectIEquatable() + { + var code = @" +using UnityEngine; +using System; + + +struct [|S|] : IEquatable +{ + public bool Equals(int other) { return false; } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.StructShouldImplementIEquatable); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsGetHashCodeAnalyzerTest.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsGetHashCodeAnalyzerTest.cs new file mode 100644 index 0000000..034e50e --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsGetHashCodeAnalyzerTest.cs @@ -0,0 +1,72 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.Language +{ + [TestFixture] + sealed class StructOverrideGetHashCodeTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new StructOverrideGetHashCodeAnalyzer(); + + + [Test] + public void StructDoNotOverrideAnything() + { + var code = @" +using UnityEngine; + +struct [|S|] +{ +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.StructShouldOverrideGetHashCode); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void StructOverridGetHashCode() + { + var code = @" +using UnityEngine; + +struct [|S|] +{ + public override int GetHashCode() { return 0; } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.StructShouldOverrideGetHashCode); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsObjectAnalyzerTest.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsObjectAnalyzerTest.cs new file mode 100644 index 0000000..118e55a --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Language/StructOverrideEqualsObjectAnalyzerTest.cs @@ -0,0 +1,70 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.Language +{ + [TestFixture] + sealed class StructOverrideEqualsObjectAnalyzerTests : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new StructOverrideEqualsObjectAnalyzer(); + + + [Test] + public void StructDoNotOverrideAnything() + { + var code = @" +using UnityEngine; + +struct [|S|] +{ +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.StructShouldOverrideEquals); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void StructOverrideEqualsObject() + { + var code = @" +using UnityEngine; + +struct [|S|] +{ + public override bool Equals(object obj) { return false; } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.StructShouldOverrideEquals); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/LogicError/InfiniteRecursiveCallTest.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/LogicError/InfiniteRecursiveCallTest.cs new file mode 100644 index 0000000..00e48fd --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/LogicError/InfiniteRecursiveCallTest.cs @@ -0,0 +1,396 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Language; +using UnityEngineAnalyzer.LogicError; + + +//using Microsoft.CodeAnalysis.Workspaces; + +namespace UnityEngineAnalyzer.Test.LogicError +{ + [TestFixture] + sealed class InfiniteRecursiveCallTest : AnalyzerTestFixture + { + + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new InfiniteRecursiveCallAnalyzer(); + + [Test] + public void InfiniteRecursiveCall1() + { + var code = @" +class A +{ + void M1() + { + [|M1()|]; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void InfiniteRecursiveCall2() + { + var code = @" +class A +{ + void M1() + { + [|M2()|]; + } + void M2() + { + M1(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void InfiniteRecursiveCall3() + { + var code = @" +class A +{ + void M1() + { + [|M2()|]; + } + void M2() + { + M3(); + } + void M3() + { + M1(); + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void InfiniteRecursiveCallDouble() + { + var code = @" +class A +{ + void M1() + { + [|M2()|]; + } + void M2() + { + M1(); + } + void M3() + { + [|M3()|]; + } + +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + HasDiagnostic(document, span, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void InfiniteRecursiveCallWithBranch1() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + if(m_bool) + { + [|M1()|]; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + + [Test] + public void InfiniteRecursiveCallWithBranch2() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + switch(m_bool) + { + default: + [|M1()|]; + break; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void InfiniteRecursiveCallWithBranch3() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + switch(m_bool) + { + default: + [|M1()|]; + break; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void InfiniteRecursiveCallWithBranch4() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + int[] a = new int[3]; + foreach(var oneInt in a) + { + [|M1()|]; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void InfiniteRecursiveCallWithBranch5() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + for(int i = 0; i < 3; ++i) + { + [|M1()|]; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void InfiniteRecursiveCallWithBranch6() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + while(true) + { + [|M1()|]; + } + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + [Test] + public void InfiniteRecursiveCallWithBranch7() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + return; + [|M1()|]; + } +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + + + [Test] + public void NotInfiniteRecursiveCall() + { + var code = @" +class A +{ + private bool m_bool; + void M1() + { + [|M2()|]; + } + + void M2() {} +}"; + + Document document; + TextSpan span; + + if (TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, + out document, out span)) + { + NoDiagnostic(document, DiagnosticIDs.InfiniteRecursiveCall); + } + else + { + Assert.Fail("Could not load unit test code"); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Material/DoNotUseStringPropertyNamesTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Material/DoNotUseStringPropertyNamesTests.cs new file mode 100644 index 0000000..7867e5c --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Material/DoNotUseStringPropertyNamesTests.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Material; + +namespace UnityEngineAnalyzer.Test.Material +{ + + [TestFixture] + sealed class DoNotUseStringPropertyNamesAnalyzerTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseStringPropertyNamesAnalyzer(); + + [Test] + public void MaterialGetFloatWithStringProperty() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Material material; + + void Start() + { + [|material.GetFloat(""_Shininess"")|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringPropertyNamesInMaterial); + } + + [Test] + public void MaterialSetFloatWithStringProperty() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Material material; + + void Start() + { + [|material.SetFloat(""_Shininess"", 1.2f)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringPropertyNamesInMaterial); + } + + [Test] + public void MaterialSetMatrixWithStringProperty() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Material material; + + void Start() + { + [|material.SetVector(""_WaveAndDistance"", Vector3.one)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringPropertyNamesInMaterial); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/MetadataReferenceHelper.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/MetadataReferenceHelper.cs index 7f4f5fc..35aafdf 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/MetadataReferenceHelper.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/MetadataReferenceHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.CodeAnalysis; using System.Collections.Immutable; using System.IO; @@ -14,7 +14,7 @@ static class MetadataReferenceHelper private static MetadataReference GetUnityMetadataReference() { - var unityEnginePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Unity\Editor\Data\Managed", "UnityEngine.dll"); + var unityEnginePath = Path.Combine(@"D:\Program Files", @"Unity2017.1.3p2\Editor\Data\Managed", "UnityEngine.dll"); return MetadataReference.CreateFromFile(unityEnginePath); } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Physics/UseNonAllocMethodsTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Physics/UseNonAllocMethodsTests.cs new file mode 100644 index 0000000..c295f05 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Physics/UseNonAllocMethodsTests.cs @@ -0,0 +1,107 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Physics; + +namespace UnityEngineAnalyzer.Test.Animator +{ + + [TestFixture] + sealed class UseNonAllocMethodsTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new UseNonAllocMethodsAnalyzer(); + + [Test] + public void PhysicsRaycastAll() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + RaycastHit[] hits; + hits = [|Physics.RaycastAll(transform.position, transform.forward, 100.0F)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.PhysicsUseNonAllocMethods); + } + + [Test] + public void Physics2DRaycastAll() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Update() + { + RaycastHit2D[] hits; + hits = [|Physics2D.RaycastAll(Vector2.zero, Vector2.one)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.PhysicsUseNonAllocMethods); + } + + [Test] + public void Physics2DCircleCastAll() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Start() + { + [|Physics2D.CircleCastAll(Vector2.zero, 5, Vector2.one)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.PhysicsUseNonAllocMethods); + } + + [Test] + public void PhysicsUseNonAllocMethods_Should_Notice_Multiple_Calls() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|Physics2D.CircleCastAll(Vector2.zero, 5, Vector2.one)|]; + + [|Physics2D.LinecastAll(Vector2.zero, Vector2.one)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.PhysicsUseNonAllocMethods); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/DoNotUseStringMethodsAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/DoNotUseStringMethodsAnalyzerTests.cs index 3a86930..98a9816 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/DoNotUseStringMethodsAnalyzerTests.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/DoNotUseStringMethodsAnalyzerTests.cs @@ -186,83 +186,5 @@ class C : MonoBehaviour HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringMethods); } - - [Test] - public void InvokeUsedInMonoBehaviourClass() - { - const string code = @" -using UnityEngine; - -class C : MonoBehaviour -{ - void Start() { [|Invoke(string.Empty, 0f)|]; } -}"; - - Document document; - TextSpan span; - TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); - - HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringMethods); - } - - [Test] - public void InvokeUsedByMonoBehaviourClass() - { - const string code = @" -using UnityEngine; - -class CC : MonoBehaviour { } - -class C : MonoBehaviour -{ - private CC cc; - void Start() { [|cc.Invoke(string.Empty, 0f)|]; } -}"; - - Document document; - TextSpan span; - TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); - - HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringMethods); - } - - [Test] - public void InvokeRepeatingUsedInMonoBehaviourClass() - { - const string code = @" -using UnityEngine; - -class C : MonoBehaviour -{ - void Start() { [|InvokeRepeating(string.Empty, 0f, 0f)|]; } -}"; - - Document document; - TextSpan span; - TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); - - HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringMethods); - } - - [Test] - public void InvokeRepeatingUsedByMonoBehaviourClass() - { - const string code = @" -using UnityEngine; - -class CC : MonoBehaviour { } - -class C : MonoBehaviour -{ - private CC cc; - void Start() { [|cc.InvokeRepeating(string.Empty, 0f, 0f)|]; } -}"; - - Document document; - TextSpan span; - TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); - - HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStringMethods); - } } } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/InvokeFunctionMissingAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/InvokeFunctionMissingAnalyzerTests.cs new file mode 100644 index 0000000..e1c4939 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/StringMethods/InvokeFunctionMissingAnalyzerTests.cs @@ -0,0 +1,94 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.StringMethods; + +namespace UnityEngineAnalyzer.Test.StringMethods +{ + [TestFixture] + sealed class InvokeFunctionMissingAnalyzerTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new InvokeFunctionMissingAnalyzer(); + + [Test] + public void InvokeUsedInMonoBehaviourClass() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Start() { [|Invoke(string.Empty, 0f)|]; } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.InvokeFunctionMissing); + } + + [Test] + public void InvokeUsedByMonoBehaviourClass() + { + const string code = @" +using UnityEngine; + +class CC : MonoBehaviour { } + +class C : MonoBehaviour +{ + private CC cc; + void Start() { [|cc.Invoke(string.Empty, 0f)|]; } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.InvokeFunctionMissing); + } + + [Test] + public void InvokeRepeatingUsedInMonoBehaviourClass() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + void Start() { [|InvokeRepeating(string.Empty, 0f, 0f)|]; } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.InvokeFunctionMissing); + } + + [Test] + public void InvokeRepeatingUsedByMonoBehaviourClass() + { + const string code = @" +using UnityEngine; + +class CC : MonoBehaviour { } + +class C : MonoBehaviour +{ + private CC cc; + void Start() { [|cc.InvokeRepeating(string.Empty, 0f, 0f)|]; } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.InvokeFunctionMissing); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj index d5501b0..cc10cc7 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj @@ -21,7 +21,7 @@ bin\Debug\ DEBUG;TRACE prompt - 4 + 0 false @@ -34,6 +34,9 @@ false + + ..\..\packages\JetBrains.dotMemoryUnit.3.0.20171219.105559\lib\net35\dotMemory.Unit.dll + ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll True @@ -124,30 +127,46 @@ false + + ..\..\..\..\..\..\..\..\Program Files\Unity2017.1.3p2\Editor\Data\Managed\UnityEngine.dll + + + + + + + + + + + + + + + + + + + + - - - {BFB2BB34-FED8-48CC-9B83-A6E38BA5666C} - UnityEngineAnalyzer - - @@ -155,7 +174,12 @@ - + + + {bfb2bb34-fed8-48cc-9b83-a6e38ba5666c} + UnityEngineAnalyzer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Animator.StringToHash can convert your state name to hash, it's faster than string comparison + An optional longer localizable description of the diagnostic. + + + Use stateNameHash instead of stateName + The format-able message the diagnostic displays. + + + Use stateNameHash instead of stateName + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainAnalyzer.cs new file mode 100644 index 0000000..9a1042c --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainAnalyzer.cs @@ -0,0 +1,98 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace UnityEngineAnalyzer.Camera +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class CameraMainAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.CameraMainIsSlow); + private List searched = new List(); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration); + } + + public void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + var monoBehaviourInfo = new MonoBehaviourInfo(context); + + monoBehaviourInfo.ForEachUpdateMethod((updateMethod) => + { + SearchCameraMain(context, updateMethod); + RecursiveMethodCrawler(context, updateMethod); + }); + } + + private void SearchCameraMain(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax method) + { + var memberAccessExpression = method.DescendantNodes().OfType(); + + foreach (var memberAccess in memberAccessExpression) + { + if (searched.Contains(memberAccess)) + { + return; + } + + searched.Add(memberAccess); + + SymbolInfo symbolInfo; + if (!context.TryGetSymbolInfo(memberAccess.Expression, out symbolInfo)) + { + continue; + } + + var containingClass = symbolInfo.Symbol.ContainingType; + + if (containingClass != null && containingClass.ContainingNamespace.Name.Equals("UnityEngine") && containingClass.Name.Equals("Camera")) + { + if (symbolInfo.Symbol.Name.Equals("main")) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.CameraMainIsSlow, memberAccess.GetLocation(), memberAccess.Name, symbolInfo.Symbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } + + private object lastMethodRecursion = null; + + private void RecursiveMethodCrawler(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax method) + { + var invocationAccessExpression = method.DescendantNodes().OfType(); + SearchCameraMain(context, method); + + foreach (var invocation in invocationAccessExpression) + { + if (lastMethodRecursion == invocationAccessExpression) + { + break; + } + + lastMethodRecursion = invocationAccessExpression; + + SymbolInfo symbolInfo; + if (!context.TryGetSymbolInfo(invocation, out symbolInfo)) + { + continue; + } + + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + var methodDeclarations = methodSymbol.DeclaringSyntaxReferences; + + foreach (var methodDeclaration in methodDeclarations) + { + var theMethodSyntax = methodDeclaration.GetSyntax() as MethodDeclarationSyntax; + RecursiveMethodCrawler(context, theMethodSyntax); + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.Designer.cs new file mode 100644 index 0000000..a28aa7d --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Camera { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CameraMainResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CameraMainResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Camera.CameraMainResource", typeof(CameraMainResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Camera.main uses FindByTag<"MainCamera"> cache this call instead.. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Camera.main is slow. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Camera.main is slow. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.resx new file mode 100644 index 0000000..82e89ea --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/CameraMainResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Camera.main uses FindByTag<"MainCamera"> cache this call instead. + An optional longer localizable description of the diagnostic. + + + Camera.main is slow + The format-able message the diagnostic displays. + + + Camera.main is slow + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/DoNotUseFindMethodsInUpdateResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/DoNotUseFindMethodsInUpdateResources.Designer.cs new file mode 100644 index 0000000..1b7dc35 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Camera/DoNotUseFindMethodsInUpdateResources.Designer.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Camera { + using System; + using System.Reflection; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotUseFindMethodsInUpdateResources { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotUseFindMethodsInUpdateResources() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("UnityEngineAnalyzer.Camera.DoNotUseFindMethodsInUpdateResources", typeof(DoNotUseFindMethodsInUpdateResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + internal static string MessageFormatRecursive { + get { + return ResourceManager.GetString("MessageFormatRecursive", resourceCulture); + } + } + + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetection.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetection.cs new file mode 100644 index 0000000..f898d18 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetection.cs @@ -0,0 +1,121 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.Language +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DuplicatedDelegateDetection : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction( + AnalysisDelegate, + SyntaxKind.DelegateDeclaration); + context.RegisterSyntaxNodeAction( + AnalysisDelegateArgument, + SyntaxKind.Parameter); + context.RegisterSyntaxNodeAction( + AnalysisDeclaration, + SyntaxKind.VariableDeclaration); + } + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create( + DiagnosticDescriptors.DuplicateDelegateDetection, + DiagnosticDescriptors.DuplicateDelegateParamDetection + ); + + private void AnalysisDelegateArgument(SyntaxNodeAnalysisContext context) + { + var paramNode = (ParameterSyntax)context.Node; + if (paramNode.Type == null) return; + var paramTypeSymbol = context.SemanticModel?.GetSymbolInfo(paramNode.Type).Symbol; + + if (paramTypeSymbol == null) return; + + if (paramTypeSymbol is ITypeSymbol typeSymbol) + { + if(IsDelegate(typeSymbol)) + context.ReportDiagnostic(Diagnostic.Create( + SupportedDiagnostics[1], + paramNode.GetLocation(), + paramTypeSymbol.MetadataName)); + } + } + + private void AnalysisDeclaration(SyntaxNodeAnalysisContext context) + { + var declaration = (VariableDeclarationSyntax) context.Node; + if (declaration.Type == null) return; + var paramTypeSymbol = context.SemanticModel?.GetSymbolInfo(declaration.Type).Symbol; + + if (paramTypeSymbol == null) return; + + if (paramTypeSymbol is ITypeSymbol typeSymbol) + { + if (IsDelegate(typeSymbol)) + context.ReportDiagnostic(Diagnostic.Create( + SupportedDiagnostics[1], + declaration.Type.GetLocation(), + paramTypeSymbol.MetadataName)); + } + } + + private void AnalysisDelegate(SyntaxNodeAnalysisContext context) + { + var declaration = (DelegateDeclarationSyntax) context.Node; + if (declaration.ReturnType == null) return; + var returnSymbol = context.SemanticModel?.GetSymbolInfo(declaration.ReturnType).Symbol.MetadataName; + + if (returnSymbol == null) return; + + var paramsSymbols = + ( + from paramsInfo in declaration.ParameterList.Parameters + select context.SemanticModel.GetSymbolInfo(paramsInfo.Type).Symbol.MetadataName + ).ToList(); + + var baseType = new StringBuilder(returnSymbol == "Void" ? "Action" : "Func"); + if(returnSymbol !="Void") + paramsSymbols.Insert(0, returnSymbol); + + if (paramsSymbols.Count > 0) + { + baseType.AppendFormat("<{0}>", string.Join(", ", paramsSymbols)); + } + + context.ReportDiagnostic(Diagnostic.Create( + SupportedDiagnostics.First(), + declaration.GetLocation(), + baseType.ToString())); + } + + private static bool IsDelegate(ITypeSymbol typeSymbol) + { + if (typeSymbol == null) return false; + + var currentType = typeSymbol; + while (currentType.MetadataName != "Object") + { + if (currentType.MetadataName.StartsWith("Func") || + currentType.MetadataName.StartsWith("Action")) + return false; + + if (currentType.MetadataName == "Delegate") + { + return true; + } + currentType = currentType.BaseType; + if (currentType == null) + return false; + } + + return false; + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.Designer.cs new file mode 100644 index 0000000..84013d7 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.Designer.cs @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Delegates { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DuplicatedDelegateDetectionResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DuplicatedDelegateDetectionResource() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Delegates.DuplicatedDelegateDetectionResource", typeof(DuplicatedDelegateDetectionResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Use common generic delegates instead of customed delegate can decrease code size. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 Use {0} replace this customed declaration delegate. 的本地化字符串。 + /// + internal static string FoundDuplicateDelegate { + get { + return ResourceManager.GetString("FoundDuplicateDelegate", resourceCulture); + } + } + + /// + /// 查找类似 Use Action or Func Delegate replace {0} delegate. 的本地化字符串。 + /// + internal static string FoundDuplicateDelegateAsParam { + get { + return ResourceManager.GetString("FoundDuplicateDelegateAsParam", resourceCulture); + } + } + + /// + /// 查找类似 Use Action or Func Delegate replace {0} delegate. 的本地化字符串。 + /// + internal static string FoundDuplicateDelegateVariable { + get { + return ResourceManager.GetString("FoundDuplicateDelegateVariable", resourceCulture); + } + } + + /// + /// 查找类似 Use Action and Func delegates replace customed delegates. 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.resx new file mode 100644 index 0000000..e823702 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/DuplicatedDelegateDetectionResource.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Use common generic delegates instead of customed delegate can decrease code size. + + + Use {0} replace this customed declaration delegate. + + + Use Action or Func Delegate replace {0} delegate. + + + Use Action or Func Delegate replace {0} delegate. + + + Use Action and Func delegates replace customed delegates. + + diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateAnalyzer.cs new file mode 100644 index 0000000..160988e --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateAnalyzer.cs @@ -0,0 +1,112 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace UnityEngineAnalyzer.Delegates +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class ShouldCacheDelegateAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.ShouldCacheDelegate); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration); + } + + public static void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + var monoBehaviourInfo = new MonoBehaviourInfo(context); + + var searched = new Dictionary(); + monoBehaviourInfo.ForEachUpdateMethod((updateMethod) => + { + AnalyzeAssignmentNode(context, updateMethod); + AnalyzeInvocationNode(context, updateMethod); + }); + } + + public static event Action e; + private static void AnalyzeAssignmentNode(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax method) + { + e += ShouldCacheDelegateAnalyzer_e; + e -= ShouldCacheDelegateAnalyzer_e; + foreach (var oneAssign in method.DescendantNodes().OfType()) + { + AnalyzeAddRemoveNode(context, oneAssign); + } + } + + private static void ShouldCacheDelegateAnalyzer_e(int obj) + { + throw new NotImplementedException(); + } + + private static void AnalyzeAddRemoveNode(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) + { + foreach (var oneIdSyntax in syntax.ChildNodes().OfType()) + { + var oneIdSymbol = context.SemanticModel.GetSymbolInfo(oneIdSyntax); + if (oneIdSymbol.Symbol != null) + { + if (oneIdSymbol.Symbol is IMethodSymbol methodSymbol && !methodSymbol.IsStatic) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ShouldCacheDelegate, + oneIdSyntax.GetLocation(), oneIdSyntax.ToString()); + context.ReportDiagnostic(diagnostic); + } + } + } + foreach (var oneAccessSyntax in syntax.ChildNodes().OfType()) + { + foreach (var oneIdSyntax in oneAccessSyntax.ChildNodes().OfType()) + { + var oneIdSymbol = context.SemanticModel.GetSymbolInfo(oneIdSyntax); + if (oneIdSymbol.Symbol != null) + { + if (oneIdSymbol.Symbol is IMethodSymbol methodSymbol && !methodSymbol.IsStatic) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ShouldCacheDelegate, + oneIdSyntax.GetLocation(), oneIdSyntax.ToString()); + context.ReportDiagnostic(diagnostic); + } + } + } + } + } + + + private static void AnalyzeInvocationNode(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax method) + { + foreach(var invoke in method.DescendantNodes().OfType()) + { + var argumentListSyntax = invoke.ChildNodes().OfType(); + foreach (var oneArgListSyntax in argumentListSyntax) + { + foreach (var oneArgSyntax in oneArgListSyntax.ChildNodes().OfType()) + { + foreach (var oneIdSyntax in oneArgSyntax.ChildNodes().OfType()) + { + var oneIdSymbol = context.SemanticModel.GetSymbolInfo(oneIdSyntax); + if (oneIdSymbol.Symbol != null) + { + if (oneIdSymbol.Symbol is IMethodSymbol methodSymbol && !methodSymbol.IsStatic) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ShouldCacheDelegate, + oneIdSyntax.GetLocation(), oneIdSyntax.ToString()); + context.ReportDiagnostic(diagnostic); + } + } + } + } + } + } + + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.Designer.cs new file mode 100644 index 0000000..dc6d158 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Delegates { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ShouldCacheDelegateResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ShouldCacheDelegateResource() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Delegates.ShouldCacheDelegateResource", typeof(ShouldCacheDelegateResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Should use variable to cache delegate for future access. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 Each time you access delegate "{0}" will cause gc allocation. You should use variable to cache delegate "{0}" for future access. 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 Should cache delegate 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.resx new file mode 100644 index 0000000..eaf8ccc --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Delegates/ShouldCacheDelegateResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Should use variable to cache delegate for future access. + An optional longer localizable description of the diagnostic. + + + Each time you access delegate "{0}" will cause gc allocation. You should use variable to cache delegate "{0}" for future access. + The format-able message the diagnostic displays. + + + Should cache delegate + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticCategories.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticCategories.cs index 029cfc1..677e2cd 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticCategories.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticCategories.cs @@ -7,6 +7,7 @@ static class DiagnosticCategories public const string StringMethods = "String Methods"; public const string Miscellaneous = "Miscellaneous"; public const string Performance = "Performance"; + public const string LogicError = "Logic Error"; public const string AOT = "AOT"; } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs index 5e40115..600739b 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs @@ -1,118 +1,182 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; +using System; +using System.Linq; +using System.Resources; +using UnityEngineAnalyzer.Animator; using UnityEngineAnalyzer.AOT; +using UnityEngineAnalyzer.Camera; using UnityEngineAnalyzer.CompareTag; using UnityEngineAnalyzer.Coroutines; using UnityEngineAnalyzer.EmptyMonoBehaviourMethods; using UnityEngineAnalyzer.FindMethodsInUpdate; using UnityEngineAnalyzer.ForEachInUpdate; +using UnityEngineAnalyzer.IL2CPP; +using UnityEngineAnalyzer.Material; using UnityEngineAnalyzer.OnGUI; +using UnityEngineAnalyzer.Physics; using UnityEngineAnalyzer.StringMethods; +using UnityEngineAnalyzer.Language; +using UnityEngineAnalyzer.GCAlloc; +using UnityEngineAnalyzer.Generics; +using UnityEngineAnalyzer.Delegates; +using UnityEngineAnalyzer.Lambda; +using UnityEngineAnalyzer.LogicError; namespace UnityEngineAnalyzer { - static class DiagnosticDescriptors + public static class DiagnosticDescriptors { - //NOTES: Naming of Descriptors are a bit inconsistant - - public static readonly DiagnosticDescriptor DoNotUseOnGUI = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseOnGUI, - title: new LocalizableResourceString(nameof(DoNotUseOnGUIResources.Title), DoNotUseOnGUIResources.ResourceManager, typeof(DoNotUseOnGUIResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseOnGUIResources.MessageFormat), DoNotUseOnGUIResources.ResourceManager, typeof(DoNotUseOnGUIResources)), - category: DiagnosticCategories.GC, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseOnGUIResources.Description), DoNotUseOnGUIResources.ResourceManager, typeof(DoNotUseOnGUIResources))); - - public static readonly DiagnosticDescriptor DoNotUseStringMethods = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseStringMethods, - title: new LocalizableResourceString(nameof(DoNotUseStringMethodsResources.Title), DoNotUseStringMethodsResources.ResourceManager, typeof(DoNotUseStringMethodsResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseStringMethodsResources.MessageFormat), DoNotUseStringMethodsResources.ResourceManager, typeof(DoNotUseStringMethodsResources)), - category: DiagnosticCategories.StringMethods, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseStringMethodsResources.Description), DoNotUseStringMethodsResources.ResourceManager, typeof(DoNotUseStringMethodsResources))); - - public static readonly DiagnosticDescriptor DoNotUseCoroutines = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseCoroutines, - title: new LocalizableResourceString(nameof(DoNotUseCoroutinesResources.Title), DoNotUseCoroutinesResources.ResourceManager, typeof(DoNotUseCoroutinesResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseCoroutinesResources.MessageFormat), DoNotUseCoroutinesResources.ResourceManager, typeof(DoNotUseCoroutinesResources)), - category: DiagnosticCategories.GC, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseCoroutinesResources.Description), DoNotUseCoroutinesResources.ResourceManager, typeof(DoNotUseCoroutinesResources))); - - - public static readonly DiagnosticDescriptor EmptyMonoBehaviourMethod = new DiagnosticDescriptor( - id: DiagnosticIDs.EmptyMonoBehaviourMethod, - title: new LocalizableResourceString(nameof(EmptyMonoBehaviourMethodsResources.Title), EmptyMonoBehaviourMethodsResources.ResourceManager, typeof(EmptyMonoBehaviourMethodsResources)), - messageFormat: new LocalizableResourceString(nameof(EmptyMonoBehaviourMethodsResources.MessageFormat), EmptyMonoBehaviourMethodsResources.ResourceManager, typeof(EmptyMonoBehaviourMethodsResources)), - category: DiagnosticCategories.Miscellaneous, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(EmptyMonoBehaviourMethodsResources.Description), EmptyMonoBehaviourMethodsResources.ResourceManager, typeof(EmptyMonoBehaviourMethodsResources))); - - public static readonly DiagnosticDescriptor UseCompareTag = new DiagnosticDescriptor( - id: DiagnosticIDs.UseCompareTag, - title: new LocalizableResourceString(nameof(UseCompareTagResources.Title), UseCompareTagResources.ResourceManager, typeof(UseCompareTagResources)), - messageFormat: new LocalizableResourceString(nameof(UseCompareTagResources.MessageFormat), UseCompareTagResources.ResourceManager, typeof(UseCompareTagResources)), - category: DiagnosticCategories.GC, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(UseCompareTagResources.Description), UseCompareTagResources.ResourceManager, typeof(UseCompareTagResources))); - - public static readonly DiagnosticDescriptor DoNotUseFindMethodsInUpdate = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseFindMethodsInUpdate, - title: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.Title), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.MessageFormat), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources)), - category: DiagnosticCategories.Performance, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.Description), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources))); - - public static readonly DiagnosticDescriptor DoNotUseFindMethodsInUpdateRecursive = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseFindMethodsInUpdate, - title: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.Title), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.MessageFormatRecursive), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources)), - category: DiagnosticCategories.Performance, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseFindMethodsInUpdateResources.Description), DoNotUseFindMethodsInUpdateResources.ResourceManager, typeof(DoNotUseFindMethodsInUpdateResources))); - - public static readonly DiagnosticDescriptor DoNotUseRemoting = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseRemoting, - title: new LocalizableResourceString(nameof(DoNotUseRemotingResources.Title), DoNotUseRemotingResources.ResourceManager, typeof(DoNotUseRemotingResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseRemotingResources.MessageFormat), DoNotUseRemotingResources.ResourceManager, typeof(DoNotUseRemotingResources)), - category: DiagnosticCategories.AOT, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseRemotingResources.Description), DoNotUseRemotingResources.ResourceManager, typeof(DoNotUseRemotingResources))); - - public static readonly DiagnosticDescriptor DoNotUseReflectionEmit = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseReflectionEmit, - title: new LocalizableResourceString(nameof(DoNotUseReflectionEmitResources.Title), DoNotUseReflectionEmitResources.ResourceManager, typeof(DoNotUseReflectionEmitResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseReflectionEmitResources.MessageFormat), DoNotUseReflectionEmitResources.ResourceManager, typeof(DoNotUseReflectionEmitResources)), - category: DiagnosticCategories.AOT, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseReflectionEmitResources.Description), DoNotUseReflectionEmitResources.ResourceManager, typeof(DoNotUseReflectionEmitResources))); - - public static readonly DiagnosticDescriptor TypeGetType = new DiagnosticDescriptor( - id: DiagnosticIDs.TypeGetType, - title: new LocalizableResourceString(nameof(TypeGetTypeResources.Title), TypeGetTypeResources.ResourceManager, typeof(TypeGetTypeResources)), - messageFormat: new LocalizableResourceString(nameof(TypeGetTypeResources.MessageFormat), TypeGetTypeResources.ResourceManager, typeof(TypeGetTypeResources)), - category: DiagnosticCategories.AOT, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(TypeGetTypeResources.Description), TypeGetTypeResources.ResourceManager, typeof(TypeGetTypeResources))); - - public static readonly DiagnosticDescriptor DoNotUseForEachInUpdate = new DiagnosticDescriptor( - id: DiagnosticIDs.DoNotUseForEachInUpdate, - title: new LocalizableResourceString(nameof(DoNotUseForEachInUpdateResources.Title), DoNotUseForEachInUpdateResources.ResourceManager, typeof(DoNotUseForEachInUpdateResources)), - messageFormat: new LocalizableResourceString(nameof(DoNotUseForEachInUpdateResources.MessageFormat), DoNotUseForEachInUpdateResources.ResourceManager, typeof(DoNotUseForEachInUpdateResources)), - category: DiagnosticCategories.Performance, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: new LocalizableResourceString(nameof(DoNotUseForEachInUpdateResources.Description), DoNotUseForEachInUpdateResources.ResourceManager, typeof(DoNotUseForEachInUpdateResources)) - ); + public static readonly DiagnosticDescriptor DoNotUseOnGUI; + public static readonly DiagnosticDescriptor DoNotUseStringMethods; + public static readonly DiagnosticDescriptor DoNotUseCoroutines; + public static readonly DiagnosticDescriptor EmptyMonoBehaviourMethod; + public static readonly DiagnosticDescriptor UseCompareTag; + public static readonly DiagnosticDescriptor DoNotUseFindMethodsInUpdate; + public static readonly DiagnosticDescriptor DoNotUseFindMethodsInUpdateRecursive; + public static readonly DiagnosticDescriptor DoNotUseRemoting; + public static readonly DiagnosticDescriptor DoNotUseReflectionEmit; + public static readonly DiagnosticDescriptor TypeGetType; + public static readonly DiagnosticDescriptor DoNotUseForEachInUpdate; + public static readonly DiagnosticDescriptor UnsealedDerivedClass; + public static readonly DiagnosticDescriptor InvokeFunctionMissing; + public static readonly DiagnosticDescriptor DoNotUseStateName; + public static readonly DiagnosticDescriptor DoNotUseStringPropertyNames; + public static readonly DiagnosticDescriptor UseNonAllocMethods; + public static readonly DiagnosticDescriptor CameraMainIsSlow; + public static readonly DiagnosticDescriptor DoNotGCAllocnInUpdate; + public static readonly DiagnosticDescriptor DoNotGCAllocnInUpdateRecursive; + public static readonly DiagnosticDescriptor DoNotBoxWhenInvoke; + public static readonly DiagnosticDescriptor StructShouldImplementIEquatable; + public static readonly DiagnosticDescriptor StructShouldOverrideEquals; + public static readonly DiagnosticDescriptor StructShouldOverrideGetHashCode; + public static readonly DiagnosticDescriptor DoNotUseEnumTypeParameter; + public static readonly DiagnosticDescriptor ShouldCacheDelegate; + public static readonly DiagnosticDescriptor InfiniteRecursiveCall; + public static readonly DiagnosticDescriptor InfiniteRecursiveCallRecursive; + public static readonly DiagnosticDescriptor LambdaClosure; + public static readonly DiagnosticDescriptor DuplicateDelegateDetection; + public static readonly DiagnosticDescriptor DuplicateDelegateParamDetection; + public static readonly DiagnosticDescriptor DuplicateDelegateVariableDetection; + public static readonly DiagnosticDescriptor EnumShouldManualSetMemberValue; + + static DiagnosticDescriptors() + { + //** UNITY ** + + //GC + DoNotUseOnGUI = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseOnGUI, DiagnosticCategories.GC, DiagnosticSeverity.Info); + DoNotUseStringMethods = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseStringMethods, DiagnosticCategories.GC, DiagnosticSeverity.Info); + DoNotUseCoroutines = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseCoroutines, DiagnosticCategories.GC, DiagnosticSeverity.Info); + UseCompareTag = CreateDiagnosticDescriptor(DiagnosticIDs.UseCompareTag, DiagnosticCategories.GC, DiagnosticSeverity.Warning); + UseNonAllocMethods = CreateDiagnosticDescriptor(DiagnosticIDs.PhysicsUseNonAllocMethods, DiagnosticCategories.GC, DiagnosticSeverity.Warning, UnityVersion.UNITY_5_3); + CameraMainIsSlow = CreateDiagnosticDescriptor(DiagnosticIDs.CameraMainIsSlow, DiagnosticCategories.GC, DiagnosticSeverity.Warning); + DoNotGCAllocnInUpdate = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotGCAllocInUpdate, nameof(DoNotGCAllocInUpdateResources.MessageFormat), DiagnosticCategories.GC, DiagnosticSeverity.Warning); + DoNotGCAllocnInUpdateRecursive = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotGCAllocInUpdate, nameof(DoNotGCAllocInUpdateResources.MessageFormatRecursive), DiagnosticCategories.GC, DiagnosticSeverity.Warning); + DoNotBoxWhenInvoke = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotBoxWhenInvoke, DiagnosticCategories.GC, DiagnosticSeverity.Warning); + ShouldCacheDelegate = CreateDiagnosticDescriptor(DiagnosticIDs.ShouldCacheDelegate, DiagnosticCategories.GC, DiagnosticSeverity.Warning); + + //Performance + DoNotUseFindMethodsInUpdate = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseFindMethodsInUpdate, nameof(DoNotUseFindMethodsInUpdateResources.MessageFormat), DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + DoNotUseFindMethodsInUpdateRecursive = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseFindMethodsInUpdate, nameof(DoNotUseFindMethodsInUpdateResources.MessageFormatRecursive), DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + DoNotUseForEachInUpdate = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseForEachInUpdate, DiagnosticCategories.Performance, DiagnosticSeverity.Warning, UnityVersion.UNITY_1_0, UnityVersion.UNITY_5_5); + UnsealedDerivedClass = CreateDiagnosticDescriptor(DiagnosticIDs.UnsealedDerivedClass, DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + InvokeFunctionMissing = CreateDiagnosticDescriptor(DiagnosticIDs.InvokeFunctionMissing, DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + DoNotUseStateName = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseStateNameInAnimator, DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + DoNotUseStringPropertyNames = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseStringPropertyNamesInMaterial, DiagnosticCategories.Performance, DiagnosticSeverity.Warning); + + LambdaClosure = CreateDiagnosticDescriptor( + DiagnosticIDs.LambdaUseLocalVariable, + nameof(LambdaAnalyzerResources.LambdaClosureAnalyzer), + DiagnosticCategories.Performance, + DiagnosticSeverity.Warning); + DuplicateDelegateDetection = CreateDiagnosticDescriptor( + DiagnosticIDs.UseCommonDelegate, + nameof(DuplicatedDelegateDetectionResource.FoundDuplicateDelegate), + DiagnosticCategories.Performance, + DiagnosticSeverity.Warning + ); + DuplicateDelegateParamDetection = CreateDiagnosticDescriptor( + DiagnosticIDs.UseCommonDelegate, + nameof(DuplicatedDelegateDetectionResource.FoundDuplicateDelegateAsParam), + DiagnosticCategories.Performance, + DiagnosticSeverity.Info + ); + DuplicateDelegateVariableDetection = CreateDiagnosticDescriptor( + DiagnosticIDs.UseCommonDelegate, + nameof(DuplicatedDelegateDetectionResource.FoundDuplicateDelegateVariable), + DiagnosticCategories.Performance, + DiagnosticSeverity.Info + ); + + //Logic Error + InfiniteRecursiveCall = CreateDiagnosticDescriptor(DiagnosticIDs.InfiniteRecursiveCall, nameof(InfiniteRecursiveCallResources.MessageFormat), DiagnosticCategories.LogicError, DiagnosticSeverity.Error); + InfiniteRecursiveCallRecursive = CreateDiagnosticDescriptor(DiagnosticIDs.InfiniteRecursiveCall, nameof(InfiniteRecursiveCallResources.MessageFormatRecursive), DiagnosticCategories.LogicError, DiagnosticSeverity.Error); + + + + //Miscellaneous + EmptyMonoBehaviourMethod = CreateDiagnosticDescriptor(DiagnosticIDs.EmptyMonoBehaviourMethod, DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Warning); + StructShouldImplementIEquatable = CreateDiagnosticDescriptor(DiagnosticIDs.StructShouldImplementIEquatable, nameof(StructAnalyzerResources.ShouldImplmentIEquatable), DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Warning); + StructShouldOverrideEquals = CreateDiagnosticDescriptor(DiagnosticIDs.StructShouldOverrideEquals, nameof(StructAnalyzerResources.ShouldOverrideEquals), DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Warning); + StructShouldOverrideGetHashCode = CreateDiagnosticDescriptor(DiagnosticIDs.StructShouldOverrideGetHashCode, nameof(StructAnalyzerResources.ShouldOverrideGetHashCode), DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Warning); + DoNotUseEnumTypeParameter = CreateDiagnosticDescriptor(DiagnosticIDs.DoNoUseEnumTypeParameter, DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Warning); + EnumShouldManualSetMemberValue = CreateDiagnosticDescriptor(DiagnosticIDs.EnumShouldManualSetMemberValue, DiagnosticCategories.Miscellaneous, DiagnosticSeverity.Info); + + //** AOT ** + DoNotUseRemoting = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseRemoting, DiagnosticCategories.AOT, DiagnosticSeverity.Info); + DoNotUseReflectionEmit = CreateDiagnosticDescriptor(DiagnosticIDs.DoNotUseReflectionEmit, DiagnosticCategories.AOT, DiagnosticSeverity.Info); + TypeGetType = CreateDiagnosticDescriptor(DiagnosticIDs.TypeGetType, DiagnosticCategories.AOT, DiagnosticSeverity.Info); + } + + private static DiagnosticDescriptor CreateDiagnosticDescriptor(string id, string category, DiagnosticSeverity severity, UnityVersion first = UnityVersion.UNITY_1_0, UnityVersion latest = UnityVersion.LATEST, bool isEnabledByDefault = true) + { + var resourceManager = new ResourceManager(typeof(T)); + + return new DiagnosticDescriptor( + id: id, + title: new LocalizableResourceString("Title", resourceManager, typeof(T)), + messageFormat: new LocalizableResourceString("MessageFormat", resourceManager, typeof(T)), + category: category, + defaultSeverity: severity, + isEnabledByDefault: isEnabledByDefault, + customTags: CreateUnityVersionInfo(first, latest), + description: new LocalizableResourceString("Description", resourceManager, typeof(T))); + } + + private static DiagnosticDescriptor CreateDiagnosticDescriptor(string id, string messageFormat, + string category, DiagnosticSeverity severity, UnityVersion first = UnityVersion.UNITY_1_0, UnityVersion latest = UnityVersion.LATEST, bool isEnabledByDefault = true) + { + var resourceManager = new ResourceManager(typeof(T)); + + return new DiagnosticDescriptor( + id: id, + title: new LocalizableResourceString("Title", resourceManager, typeof(T)), + messageFormat: new LocalizableResourceString(messageFormat, resourceManager, typeof(T)), + category: category, + defaultSeverity: severity, + isEnabledByDefault: isEnabledByDefault, + customTags: CreateUnityVersionInfo(first, latest), + description: new LocalizableResourceString("Description", resourceManager, typeof(T))); + } + private static string[] CreateUnityVersionInfo(UnityVersion start, UnityVersion end) + { + return new string[] { Enum.GetName(typeof(UnityVersion), start), Enum.GetName(typeof(UnityVersion), end) }; + } + + public static UnityVersionSpan GetVersion(DiagnosticDescriptor dc) + { + var list = dc.CustomTags.ToList(); + + if (list.Count < 2) + { + return new UnityVersionSpan(UnityVersion.NONE, UnityVersion.LATEST); + } + + var start = (UnityVersion)Enum.Parse(typeof(UnityVersion), list[0]); + var end = (UnityVersion)Enum.Parse(typeof(UnityVersion), list[1]); + + return new UnityVersionSpan(start, end); + } } } + diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs index aac1077..9a1bafa 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs @@ -1,4 +1,4 @@ - + namespace UnityEngineAnalyzer { public static class DiagnosticIDs @@ -10,11 +10,32 @@ public static class DiagnosticIDs public const string DoNotUseFindMethodsInUpdate = "UEA0005"; public const string DoNotUseCoroutines = "UEA0006"; public const string DoNotUseForEachInUpdate = "UEA0007"; + public const string UnsealedDerivedClass = "UEA0008"; + public const string InvokeFunctionMissing = "UEA0009"; + public const string DoNotUseStateNameInAnimator = "UEA0010"; + public const string DoNotUseStringPropertyNamesInMaterial = "UEA0011"; + public const string CameraMainIsSlow = "UEA0012"; + public const string PhysicsUseNonAllocMethods = "UEA0013"; + public const string DoNotGCAllocInUpdate = "UEA0014"; + + // language analysis + public const string StructShouldImplementIEquatable = "UCS0001"; + public const string StructShouldOverrideEquals = "UCS0002"; + public const string StructShouldOverrideGetHashCode = "UCS0003"; + public const string DoNotBoxWhenInvoke = "UCS0004"; + public const string DoNoUseEnumTypeParameter = "UCS0005"; + public const string ShouldCacheDelegate = "UCS0006"; + public const string LambdaUseLocalVariable = "UCS0007"; + public const string UseCommonDelegate = "UCS0008"; + public const string EnumShouldManualSetMemberValue = "UCS0009"; + + // logic error analysis + public const string InfiniteRecursiveCall = "ULE0001"; + //NOTES: These should probably be on their own analyzer - as they are not specific to Unity public const string DoNotUseRemoting = "AOT0001"; public const string DoNotUseReflectionEmit = "AOT0002"; public const string TypeGetType = "AOT0003"; - } } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsAnalyzer.cs index eb742f5..731a8a2 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsAnalyzer.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsAnalyzer.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -81,7 +81,7 @@ public override void Initialize(AnalysisContext context) //NOTE: It might be more officient to find classes and then determine if they are a MonoBehaviour rather than look at every method } - private static async void AnalyzeSymbol(SymbolAnalysisContext context) + private static void AnalyzeSymbol(SymbolAnalysisContext context) { // retrieve method symbol var methodSymbol = context.Symbol as IMethodSymbol; @@ -91,7 +91,7 @@ private static async void AnalyzeSymbol(SymbolAnalysisContext context) if (methodSymbol.DeclaringSyntaxReferences.Length != 1) { return; } // retrieve the method syntax from the method symbol - var methodSyntax = await methodSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync() as MethodDeclarationSyntax; + var methodSyntax = methodSymbol.DeclaringSyntaxReferences[0].GetSyntax() as MethodDeclarationSyntax; // from the method syntax, check if there is a body and if there are statements in it if (methodSyntax?.Body?.Statements.Any() ?? true) { return; } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixer.cs new file mode 100644 index 0000000..f5c7900 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/EmptyMonoBehaviourMethods/EmptyMonoBehaviourMethodsCodeFixer.cs @@ -0,0 +1,54 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace UnityEngineAnalyzer.EmptyMonoBehaviourMethods +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EmptyMonoBehaviourMethodsCodeFixer)), Shared] + public class EmptyMonoBehaviourMethodsCodeFixer : CodeFixProvider + { + private const string _title = "Remove Method"; + + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIDs.EmptyMonoBehaviourMethod); + + public override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf() + .OfType().First(); + + context.RegisterCodeFix( + CodeAction.Create( + _title, + c => RemveMethod(context.Document, declaration, c), + _title + ), + diagnostic + ); + } + + private async Task RemveMethod(Document document, MethodDeclarationSyntax declaration, CancellationToken cancellationToken) + { + var removedParent = declaration.Parent.RemoveNode(declaration, SyntaxRemoveOptions.KeepNoTrivia); + var oldRoot = await document.GetSyntaxRootAsync(cancellationToken); + var newRoot = oldRoot.ReplaceNode(declaration.Parent, removedParent); + + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.Designer.cs index 7f9db7c..e18cf8c 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.Designer.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ @@ -14,12 +14,12 @@ namespace UnityEngineAnalyzer.FindMethodsInUpdate { /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// 一个强类型的资源类,用于查找本地化的字符串等。 /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -34,7 +34,7 @@ internal DoNotUseFindMethodsInUpdateResources() { } /// - /// Returns the cached ResourceManager instance used by this class. + /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -48,8 +48,8 @@ internal DoNotUseFindMethodsInUpdateResources() { } /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -62,7 +62,7 @@ internal DoNotUseFindMethodsInUpdateResources() { } /// - /// Looks up a localized string similar to Using Find or GetComponent in Update methods can impact performance. Cache the result on Start or Awake methods. + /// 查找类似 Using Find or GetComponent in Update methods can impact performance. Cache the result on Start or Awake methods 的本地化字符串。 /// internal static string Description { get { @@ -71,7 +71,7 @@ internal static string Description { } /// - /// Looks up a localized string similar to The method {0} is called from {1}.{2} which could cause performance problems. Cache the result from {0} in Start or Awake instead.. + /// 查找类似 The method {0} is called from {1}.{2} which could cause performance problems. Cache the result from {0} in Start or Awake instead. 的本地化字符串。 /// internal static string MessageFormat { get { @@ -80,7 +80,7 @@ internal static string MessageFormat { } /// - /// Looks up a localized string similar to The method {0}.{1} calls {2} which eventually calls {3} which could impact performance. Cache the result from {3} in Start or Awake instead.. + /// 查找类似 The method {0}.{1} calls {2} which eventually calls {3} which could impact performance. Cache the result from {3}'s {2} in Start or Awake instead. 的本地化字符串。 /// internal static string MessageFormatRecursive { get { @@ -89,7 +89,7 @@ internal static string MessageFormatRecursive { } /// - /// Looks up a localized string similar to Cache the result of Find or GetComponent in Start or Awake.. + /// 查找类似 Cache the result of Find or GetComponent in Start or Awake. 的本地化字符串。 /// internal static string Title { get { diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.resx index f263459..980a5ed 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.resx +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/FindMethodsInUpdate/DoNotUseFindMethodsInUpdateResources.resx @@ -126,7 +126,7 @@ The format-able message the diagnostic displays. - The method {0}.{1} calls {2} which eventually calls {3} which could impact performance. Cache the result from {3} in Start or Awake instead. + The method {0}.{1} calls {2} which eventually calls {3} which could impact performance. Cache the result from {3}'s {2} in Start or Awake instead. Cache the result of Find or GetComponent in Start or Awake. diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/ForEachInUpdate/DoNotUseForEachInUpdate.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/ForEachInUpdate/DoNotUseForEachInUpdate.cs index 06c9321..9ac340d 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/ForEachInUpdate/DoNotUseForEachInUpdate.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/ForEachInUpdate/DoNotUseForEachInUpdate.cs @@ -23,6 +23,8 @@ public static void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) { var monoBehaviourInfo = new MonoBehaviourInfo(context); + + var searched = new Dictionary(); monoBehaviourInfo.ForEachUpdateMethod((updateMethod) => { diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNoGCAllocInUpdateAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNoGCAllocInUpdateAnalyzer.cs new file mode 100644 index 0000000..0c33c87 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNoGCAllocInUpdateAnalyzer.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace UnityEngineAnalyzer.GCAlloc +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + /// DoNotGCAllocInUpdateAnalyzer currently is conservative, + /// which means, if the ObjectCreationExpressionSyntax is inside a branch (if/switch), + /// then this analyzer will NOT report diagnose. + public sealed class DoNotGCAllocInUpdateAnalyzer : DiagnosticAnalyzer + { + + private Dictionary _indirectCallers; + + + public override ImmutableArray SupportedDiagnostics + { + get + { + return ImmutableArray.Create( + DiagnosticDescriptors.DoNotGCAllocnInUpdate, + DiagnosticDescriptors.DoNotGCAllocnInUpdateRecursive); + } + } + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration); + } + + public void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + var monoBehaviourInfo = new MonoBehaviourInfo(context); + + var searched = new Dictionary(); + _indirectCallers = new Dictionary(); + + monoBehaviourInfo.ForEachUpdateMethod((updateMethod) => + { + //Debug.WriteLine("Found an update call! " + updateMethod); + + var results = SearchForTargetExpression(context, updateMethod, searched, true); + + foreach (var oneResult in results) + { + if (!_indirectCallers.ContainsKey(oneResult)) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotGCAllocnInUpdate, + oneResult.GetLocation(), oneResult, monoBehaviourInfo.ClassName, updateMethod.Identifier); + context.ReportDiagnostic(diagnostic); + } + else + { + var endPoint = _indirectCallers[oneResult]; + + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotGCAllocnInUpdateRecursive, + oneResult.GetLocation(), monoBehaviourInfo.ClassName, updateMethod.Identifier, oneResult, endPoint); + context.ReportDiagnostic(diagnostic); + } + + } + }); + } + + //TODO: Try to simplify this method - it's very hard to follow + private IEnumerable SearchForTargetExpression(SyntaxNodeAnalysisContext context, + MethodDeclarationSyntax method, IDictionary searchedSymbol, bool isRoot) + { + var targetExps = method.DescendantNodes().OfType(); + foreach (var oneTargetExp in targetExps) + { + /* + var oneTargetIdentifierExps = oneTargetExp.DescendantNodes().OfType(); + foreach(var oneTargetIdentifierExp in oneTargetIdentifierExps) + { + } + */ + + bool isContainedByBranch = false; + + SyntaxNode parent = oneTargetExp; + while (parent != method) + { + if (parent is IfStatementSyntax || parent is SwitchStatementSyntax) + { + isContainedByBranch = true; + break; + } + + parent = parent.Parent; + } + + if (isContainedByBranch) + { + continue; + } + + + SymbolInfo oneSymbolInfo; + if (!context.TryGetSymbolInfo(oneTargetExp, out oneSymbolInfo)) + { + continue; + } + + var targetSymbol = oneSymbolInfo.Symbol as IMethodSymbol; + if (targetSymbol != null) + { + if (searchedSymbol.ContainsKey(targetSymbol)) + { + if (searchedSymbol[targetSymbol]) + { + yield return (ExpressionSyntax)oneTargetExp; + } + } + else + { + if (targetSymbol.ReceiverType.IsReferenceType) + { + searchedSymbol.Add(targetSymbol, true); + yield return oneTargetExp; + } + } + } + } + + + var invocationExps = method.DescendantNodes().OfType(); + foreach (var oneInvocationExp in invocationExps) + { + SymbolInfo oneSymbolInfo; + if (!context.TryGetSymbolInfo(oneInvocationExp, out oneSymbolInfo)) + { + continue; + } + + + bool isContainedByBranch = false; + SyntaxNode parent = oneInvocationExp; + while (parent != method) + { + if (parent is IfStatementSyntax || parent is SwitchStatementSyntax) + { + isContainedByBranch = true; + break; + } + + parent = parent.Parent; + } + if (isContainedByBranch) + { + continue; + } + + var methodSymbol = oneSymbolInfo.Symbol as IMethodSymbol; + + if (methodSymbol != null) + { + if (searchedSymbol.ContainsKey(methodSymbol)) + { + if (searchedSymbol[methodSymbol]) + { + yield return oneInvocationExp; + } + } + else + { + var methodDeclarations = methodSymbol.DeclaringSyntaxReferences; + searchedSymbol.Add(methodSymbol, false); //let's assume there won't be any calls + + foreach (var methodDeclaration in methodDeclarations) + { + var theMethodSyntax = methodDeclaration.GetSyntax() as MethodDeclarationSyntax; + + if (theMethodSyntax != null) + { + var childResults = SearchForTargetExpression(context, theMethodSyntax, searchedSymbol, false); + + if (childResults != null && childResults.Any()) + { + searchedSymbol[methodSymbol] = true; //update the searched dictionary with new info + + if (isRoot) + { + _indirectCallers.Add(oneInvocationExp, childResults.First()); + } + + yield return oneInvocationExp; + break; + } + } + } + } + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeAnalyzer.cs new file mode 100644 index 0000000..8f85033 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeAnalyzer.cs @@ -0,0 +1,89 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace UnityEngineAnalyzer.GCAlloc +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DoNotBoxWhenInvokeAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.DoNotBoxWhenInvoke); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var invocation = context.Node as InvocationExpressionSyntax; + + if (invocation?.ArgumentList == null || invocation.ArgumentList.Arguments.Count == 0) + { + return; + } + + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + + bool isConditional = false; + foreach(var oneAttribute in methodSymbol.GetAttributes()) + { + if(oneAttribute.AttributeClass.Name == "ConditionalAttribute" && oneAttribute.AttributeClass.ContainingNamespace.Name == "Diagnostics") + { + isConditional = true; + break; + } + + } + + if(isConditional) + { + return; + } + + if(methodSymbol.Parameters == null || methodSymbol.Parameters.Length == 0) + { + return; + } + + var lastParameter = methodSymbol.Parameters[methodSymbol.Parameters.Length - 1]; + if (lastParameter.IsParams) + { + if (lastParameter.Type is IArrayTypeSymbol arrSymbol && arrSymbol.ElementType.IsValueType) + return; + } + + for(int i = 0; i < methodSymbol.Parameters.Length && i < invocation.ArgumentList.Arguments.Count; ++i) + { + var oneArgSyntax = invocation.ArgumentList.Arguments[i]; + if(oneArgSyntax == null) + { + continue; + } + var oneArgType = context.SemanticModel.GetTypeInfo(oneArgSyntax.Expression); + if(oneArgType.Type == null) + { + continue; + } + + var oneParamType = methodSymbol.Parameters[i]; + + if(oneParamType.Type == null) + { + return; + } + + if(oneArgType.Type.IsValueType && oneParamType.Type.IsReferenceType) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotBoxWhenInvoke, oneArgSyntax.Expression.GetLocation(), + methodSymbol.ContainingType.Name, methodSymbol.Name, oneParamType.Name, oneParamType.Type.ToString(), + oneArgSyntax.Expression.ToString(), oneArgType.Type.ToString()); + context.ReportDiagnostic(diagnostic); + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.Designer.cs new file mode 100644 index 0000000..98a9267 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.GCAlloc { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotBoxWhenInvokeResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotBoxWhenInvokeResource() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.GCAlloc.DoNotBoxWhenInvokeResource", typeof(DoNotBoxWhenInvokeResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Avoid boxing when invoke 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 Method {0}.{1}'s parameter "{2}"'s type "{3}" is reference type, but the argument "{4}"'s type "{5}" is value type, which causes boxing and gc allocation. 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 Avoid boxing when invoke 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.resx new file mode 100644 index 0000000..5514b0a --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotBoxWhenInvokeResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Avoid boxing when invoke + An optional longer localizable description of the diagnostic. + + + Method {0}.{1}'s parameter "{2}"'s type "{3}" is reference type, but the argument "{4}"'s type "{5}" is value type, which causes boxing and gc allocation. + The format-able message the diagnostic displays. + + + Avoid boxing when invoke + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.Designer.cs new file mode 100644 index 0000000..531a9c4 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.Designer.cs @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.GCAlloc { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotGCAllocInUpdateResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotGCAllocInUpdateResources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.GCAlloc.DoNotGCAllocInUpdateResources", typeof(DoNotGCAllocInUpdateResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 GC allocation in Update methods can impact performance. Use pool or data-member to cache your data. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 {0} makes GC allocation from {1}.{2} which could cause performance problems. Use pool or data-member to cache your data. 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 The method {0}.{1} calls {2} which eventually calls {3} whose GC allocation could impact performance. Use pool or data-member to cache your data from {3} 的本地化字符串。 + /// + internal static string MessageFormatRecursive { + get { + return ResourceManager.GetString("MessageFormatRecursive", resourceCulture); + } + } + + /// + /// 查找类似 Avoid GC allocation in Update methods 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.resx new file mode 100644 index 0000000..a12743f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/GCAlloc/DoNotGCAllocInUpdateResources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + GC allocation in Update methods can impact performance. Use pool or data-member to cache your data. + An optional longer localizable description of the diagnostic. + + + {0} makes GC allocation from {1}.{2} which could cause performance problems. Use pool or data-member to cache your data. + The format-able message the diagnostic displays. + + + The method {0}.{1} calls {2} which eventually calls {3} whose GC allocation could impact performance. Use pool or data-member to cache your data from {3} + + + Avoid GC allocation in Update methods + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterAnalyzer.cs new file mode 100644 index 0000000..0423e07 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterAnalyzer.cs @@ -0,0 +1,56 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; + +namespace UnityEngineAnalyzer.Generics +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DoNotUseEnumTypeParameterAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.DoNotUseEnumTypeParameter); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.VariableDeclaration); + } + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var targetSyntax = context.Node as VariableDeclarationSyntax; + if (targetSyntax == null) + { + return; + } + + System.Collections.Generic.HashSet checkedIdSyntax = new System.Collections.Generic.HashSet(); + + var typeArgumentLists = targetSyntax.DescendantNodes().OfType(); + foreach(var oneTypeArgSyntax in typeArgumentLists) + { + foreach(var oneIdSyntax in oneTypeArgSyntax.DescendantNodes().OfType()) + { + if(!checkedIdSyntax.Contains(oneIdSyntax)) + { + checkedIdSyntax.Add(oneIdSyntax); + + var oneIdSymbolInfo = context.SemanticModel.GetSymbolInfo(oneIdSyntax); + var oneIdSymbolraw = oneIdSymbolInfo.Symbol as INamedTypeSymbol; + if (oneIdSymbolraw is INamedTypeSymbol oneIdSymbol && + oneIdSymbol.BaseType != null && + oneIdSymbol.BaseType.Name == "Enum" && + oneIdSymbol.BaseType.ContainingNamespace.Name == "System") + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotUseEnumTypeParameter, + oneIdSyntax.GetLocation(), oneIdSyntax.ToString()); + context.ReportDiagnostic(diagnostic); + } + } + } + } + + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.Designer.cs new file mode 100644 index 0000000..0b1b275 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Generics { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotUseEnumTypeParameterResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotUseEnumTypeParameterResource() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Generics.DoNotUseEnumTypeParameterResource", typeof(DoNotUseEnumTypeParameterResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Prefer to use integer type (short or int) as TypeParameter, and convert it between Enum type. This can reduce code size, and you don't need to pass additional IEqualityComparer for your Enum for your container. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 Change {0} to integer type (short or int) as TypeParameter 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 Use integer type instead of Enum as TypeParameter 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.resx new file mode 100644 index 0000000..869e6a7 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/DoNotUseEnumTypeParameterResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Prefer to use integer type (short or int) as TypeParameter, and convert it between Enum type. This can reduce code size, and you don't need to pass additional IEqualityComparer for your Enum for your container. + An optional longer localizable description of the diagnostic. + + + Change {0} to integer type (short or int) as TypeParameter + The format-able message the diagnostic displays. + + + Use integer type instead of Enum as TypeParameter + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValue.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValue.cs new file mode 100644 index 0000000..66560eb --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValue.cs @@ -0,0 +1,48 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; + +namespace UnityEngineAnalyzer.Generics +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class EnumShouldManualSetMemberValue : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.EnumShouldManualSetMemberValue); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.EnumMemberDeclaration); + } + + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var targetSyntax = context.Node as EnumMemberDeclarationSyntax; + if (targetSyntax == null) + { + return; + } + + bool hasEqualClause = false; + foreach(var oneSyntax in targetSyntax.DescendantNodes().OfType()) + { + if(oneSyntax != null) + { + hasEqualClause = true; + break; + } + } + + if(!hasEqualClause) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.EnumShouldManualSetMemberValue, + targetSyntax.GetLocation(), targetSyntax.ToString()); + context.ReportDiagnostic(diagnostic); + } + + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.Designer.cs new file mode 100644 index 0000000..6e1ba57 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Generics { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class EnumShouldManualSetMemberValueResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal EnumShouldManualSetMemberValueResource() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Generics.EnumShouldManualSetMemberValueResource", typeof(EnumShouldManualSetMemberValueResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 If this enum is used for prefab or excel, you must manually set enum member value. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 enum member "{0}" should be manually set value 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 Enum member value required 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.resx new file mode 100644 index 0000000..837e742 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Generics/EnumShouldManualSetMemberValueResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + If this enum is used for prefab or excel, you must manually set enum member value. + An optional longer localizable description of the diagnostic. + + + enum member "{0}" should be manually set value + The format-able message the diagnostic displays. + + + Enum member value required + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassAnalyzer.cs new file mode 100644 index 0000000..4dd4ad2 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassAnalyzer.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.IL2CPP +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class UnsealedDerivedClassAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.ClassDeclaration); + } + + private void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; + + if (classDeclaration.IsDerived() && !classDeclaration.IsSealed()) + { + var methods = classDeclaration.Members.OfType(); + + foreach (var method in methods) + { + + if (method.IsOverriden() && !method.IsSealed()) + { + var diagnostic = Diagnostic.Create(SupportedDiagnostics.First(), method.Identifier.GetLocation(), + method.Identifier.ToString(), classDeclaration.Identifier.ToString()); + + context.ReportDiagnostic(diagnostic); + } + } + } + } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.UnsealedDerivedClass); + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassCodeFixer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassCodeFixer.cs new file mode 100644 index 0000000..32b8144 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassCodeFixer.cs @@ -0,0 +1,65 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace UnityEngineAnalyzer.IL2CPP +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnsealedDerivedClassCodeFixer)), Shared] + public class UnsealedDerivedClassCodeFixer:CodeFixProvider + { + private const string _title = "Add sealed Modifier"; + + public override ImmutableArray FixableDiagnosticIds =>ImmutableArray.Create(DiagnosticIDs.UnsealedDerivedClass); + + public override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var declarations = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf() + .OfType().First(); + + context.RegisterCodeFix( + CodeAction.Create( + _title, + c=>AddModifier(context.Document, declarations, c), + _title + ), + diagnostic + ); + } + + private async Task AddModifier(Document contextDocument, MethodDeclarationSyntax declaration, CancellationToken cancellationToken) + { + var firstToken = declaration.GetFirstToken(); + var leadingTrivia = firstToken.LeadingTrivia; + //var trimmedDeclaration = + // declaration.ReplaceToken(firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty)); + + var sealedToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.SealedKeyword, + SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)); + + var newModifiers = declaration.Modifiers.Insert(declaration.Modifiers.Count >= 1 ? 1 : 0, sealedToken); + var newDecolation = declaration.WithModifiers(newModifiers); + + var oldRoot = await contextDocument.GetSyntaxRootAsync(cancellationToken); + var newRoot = oldRoot.ReplaceNode(declaration, newDecolation); + + return contextDocument.WithSyntaxRoot(newRoot); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.Designer.cs new file mode 100644 index 0000000..752994c --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.IL2CPP { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class UnsealedDerivedClassResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UnsealedDerivedClassResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.IL2CPP.UnsealedDerivedClassResources", typeof(UnsealedDerivedClassResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Unsealed Methods in Derived classes can impact performance. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Method {0} in Derived Class {1} is not sealed. Sealing the method or class improves performance.. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsealed Derived Class. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.resx new file mode 100644 index 0000000..cfee10d --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/IL2CPP/UnsealedDerivedClassResources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unsealed Methods in Derived classes can impact performance + An optional longer localizable description of the diagnostic. + + + Method {0} in Derived Class {1} is not sealed. Sealing the method or class improves performance. + The format-able message the diagnostic displays. + + + Unsealed Derived Class + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/IgnoringWithCommit.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/IgnoringWithCommit.cs new file mode 100644 index 0000000..c169817 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/IgnoringWithCommit.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace UnityEngineAnalyzer +{ + internal static class IgnoringWithCommit + { + private readonly static Regex IgnoreFormatRegex = new Regex(@"//\s*Ignore CA:\s*(?[0-9a-zA-Z]+)(,\s*(?[0-9a-zA-Z]+))"); + + //public static bool ShouldIgnore(SyntaxNode node, string checkId) + //{ + + //} + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.Designer.cs new file mode 100644 index 0000000..14f300b --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Lambda { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class LambdaAnalyzerResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LambdaAnalyzerResources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Lambda.LambdaAnalyzerResources", typeof(LambdaAnalyzerResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 A reference of variables in outter scope of a lambda expression will make a gerenation of a closure. It's better to avoid this in order to decrease code size, memory and gc cost. 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 A outter variable is found in a lambda expression, which may cause a closure object allocated. 的本地化字符串。 + /// + internal static string LambdaClosureAnalyzer { + get { + return ResourceManager.GetString("LambdaClosureAnalyzer", resourceCulture); + } + } + + /// + /// 查找类似 Check local variable in a lambda expression. 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.resx new file mode 100644 index 0000000..363907a --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaAnalyzerResources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A reference of variables in outter scope of a lambda expression will make a gerenation of a closure. It's better to avoid this in order to decrease code size, memory and gc cost. + + + A outter variable is found in a lambda expression, which may cause a closure object allocated. + + + Check local variable in a lambda expression. + + diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaClosureAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaClosureAnalyzer.cs new file mode 100644 index 0000000..7a7fd2e --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Lambda/LambdaClosureAnalyzer.cs @@ -0,0 +1,40 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.Language +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class LambdaClosureAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalysisLambda, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); + } + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(DiagnosticDescriptors.LambdaClosure); + + private void AnalysisLambda(SyntaxNodeAnalysisContext context) + { + var lambdaNode = (LambdaExpressionSyntax) context.Node; + var des = context.SemanticModel.GetSymbolInfo(lambdaNode).Symbol; + + var childrenNodes = + from node in lambdaNode.DescendantNodes().OfType() + let symbol = context.SemanticModel.GetSymbolInfo(node).Symbol + where symbol.Kind == SymbolKind.Local && symbol.ContainingSymbol != des + select node; + + foreach (var node in childrenNodes) + { + var diagnostic = + Diagnostic.Create(SupportedDiagnostics.First(), node.GetLocation(), node.Identifier.Text); + context.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.Designer.cs new file mode 100644 index 0000000..9d728bb --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.Designer.cs @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Language { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class StructAnalyzerResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal StructAnalyzerResources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Language.StructAnalyzerResources", typeof(StructAnalyzerResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Inappropriate Struct implementation can impact performance 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 Struct {0} should implement IEquatable to avoid boxing in case Equals(object o) is called. 的本地化字符串。 + /// + internal static string ShouldImplmentIEquatable { + get { + return ResourceManager.GetString("ShouldImplmentIEquatable", resourceCulture); + } + } + + /// + /// 查找类似 Struct {0} should override Equals(object o) because its default implementation may use reflection and is not efficient 的本地化字符串。 + /// + internal static string ShouldOverrideEquals { + get { + return ResourceManager.GetString("ShouldOverrideEquals", resourceCulture); + } + } + + /// + /// 查找类似 Struct {0} should override GetHashCode() because its default implementation may use reflection and is not efficient 的本地化字符串。 + /// + internal static string ShouldOverrideGetHashCode { + get { + return ResourceManager.GetString("ShouldOverrideGetHashCode", resourceCulture); + } + } + + /// + /// 查找类似 Inappropriate Struct implementation 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.resx new file mode 100644 index 0000000..4dace20 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructAnalyzerResources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Inappropriate Struct implementation can impact performance + An optional longer localizable description of the diagnostic. + + + Struct {0} should implement IEquatable to avoid boxing in case Equals(object o) is called. + The format-able message the diagnostic displays. + + + Struct {0} should override Equals(object o) because its default implementation may use reflection and is not efficient + + + Struct {0} should override GetHashCode() because its default implementation may use reflection and is not efficient + + + Inappropriate Struct implementation + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructImplementationAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructImplementationAnalyzer.cs new file mode 100644 index 0000000..32e5a04 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructImplementationAnalyzer.cs @@ -0,0 +1,79 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.Language +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class StructImplementAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.StructDeclaration); + } + + private void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + CheckStructShouldImplementIEquatable(context); + } + + + private void CheckStructShouldImplementIEquatable(SyntaxNodeAnalysisContext context) + { + var declSyntax = (StructDeclarationSyntax)context.Node; + var declSymbol = context.SemanticModel.GetDeclaredSymbol(declSyntax) as INamedTypeSymbol; + + bool foundIEquatableOfStruct = false; + + if (declSyntax.BaseList != null) + { + + var baseList = declSyntax.BaseList.Types; + var baseTypesSyntax = baseList.OfType(); + + + foreach (var oneBaseTypeSyntax in baseTypesSyntax) + { + var baseTypeName = oneBaseTypeSyntax.ChildNodes(); + foreach (var oneName in baseTypeName) + { + if (oneName is GenericNameSyntax) + { + var genericSymbolInfo = context.SemanticModel.GetSymbolInfo(oneName); + var genericSymbol = genericSymbolInfo.Symbol as INamedTypeSymbol; + if (genericSymbol != null && genericSymbol.ContainingNamespace.Name == "System" && + genericSymbol.Name == "IEquatable") + { + foreach (var oneParameterType in genericSymbol.TypeArguments) + { + if (oneParameterType == declSymbol) + { + foundIEquatableOfStruct = true; + } + } + } + } + } + } + } + + if (!foundIEquatableOfStruct) + { + + var diagnostic = Diagnostic.Create(SupportedDiagnostics.First(), declSyntax.Identifier.GetLocation(), + declSyntax.Identifier.ToString()); + + context.ReportDiagnostic(diagnostic); + } + } + + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create( + DiagnosticDescriptors.StructShouldImplementIEquatable + ); + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideEqualsObjectAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideEqualsObjectAnalyzer.cs new file mode 100644 index 0000000..c164e45 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideEqualsObjectAnalyzer.cs @@ -0,0 +1,68 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.Language +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class StructOverrideEqualsObjectAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.StructDeclaration); + } + + private void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + CheckStructShouldOverride(context); + } + + + private void CheckStructShouldOverride(SyntaxNodeAnalysisContext context) + { + var declSyntax = (StructDeclarationSyntax)context.Node; + + bool foundOverrideEquals = false; + + foreach (var oneChild in declSyntax.ChildNodes()) + { + if(oneChild is MethodDeclarationSyntax) + { + var oneMethodSymbol = context.SemanticModel.GetDeclaredSymbol(oneChild) as IMethodSymbol; + if(oneMethodSymbol.IsOverride) + { + if(oneMethodSymbol.Parameters != null && oneMethodSymbol.Parameters.Length == 1 && + oneMethodSymbol.Name == "Equals") + { + var oneParamType = oneMethodSymbol.Parameters[0].Type; + if (oneParamType.ContainingNamespace.Name == "System" && oneParamType.Name.ToLower() == "object") + { + foundOverrideEquals = true; + break; + } + } + + } + } + } + + + if(!foundOverrideEquals) + { + var diagnostic = Diagnostic.Create(SupportedDiagnostics[0], declSyntax.Identifier.GetLocation(), + declSyntax.Identifier.ToString()); + + context.ReportDiagnostic(diagnostic); + } + } + + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create( + DiagnosticDescriptors.StructShouldOverrideEquals + ); + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideGetHashCodeAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideGetHashCodeAnalyzer.cs new file mode 100644 index 0000000..ad7e4df --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Language/StructOverrideGetHashCodeAnalyzer.cs @@ -0,0 +1,63 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.Language +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class StructOverrideGetHashCodeAnalyzer : DiagnosticAnalyzer + { + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeClassSyntax, SyntaxKind.StructDeclaration); + } + + private void AnalyzeClassSyntax(SyntaxNodeAnalysisContext context) + { + CheckStructShouldOverride(context); + } + + + private void CheckStructShouldOverride(SyntaxNodeAnalysisContext context) + { + var declSyntax = (StructDeclarationSyntax)context.Node; + + bool foundOverridGetHashCode = false; + + foreach (var oneChild in declSyntax.ChildNodes()) + { + if(oneChild is MethodDeclarationSyntax) + { + var oneMethodSymbol = context.SemanticModel.GetDeclaredSymbol(oneChild) as IMethodSymbol; + if(oneMethodSymbol.IsOverride) + { + if ((oneMethodSymbol.Parameters == null || oneMethodSymbol.Parameters.Length == 0) && + oneMethodSymbol.Name == "GetHashCode") + { + foundOverridGetHashCode = true; + break; + } + } + } + } + + + if (!foundOverridGetHashCode) + { + var diagnostic = Diagnostic.Create(SupportedDiagnostics[0], declSyntax.Identifier.GetLocation(), + declSyntax.Identifier.ToString()); + + context.ReportDiagnostic(diagnostic); + } + } + + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create( + DiagnosticDescriptors.StructShouldOverrideGetHashCode + ); + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallAnalyzer.cs new file mode 100644 index 0000000..66bcba8 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallAnalyzer.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace UnityEngineAnalyzer.LogicError +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + /// InfiniteRecursiveCallAnalyzer currently is conservative, + /// which means, if the recursive call is inside a branch (if/switch), + /// then this analyzer will NOT report diagnose. + public sealed class InfiniteRecursiveCallAnalyzer : DiagnosticAnalyzer + { + + private Dictionary _directCallers; + private Dictionary _indirectCallers; + + + public override ImmutableArray SupportedDiagnostics + { + get + { + return ImmutableArray.Create( + DiagnosticDescriptors.InfiniteRecursiveCall, + DiagnosticDescriptors.InfiniteRecursiveCallRecursive); + } + } + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.ClassDeclaration); + } + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var searched = new Dictionary(); + _directCallers = new Dictionary(); + _indirectCallers = new Dictionary(); + + List reported = new List(); + + foreach (var oneMethodDeclaration in context.Node.ChildNodes().OfType()) + { + var methodSymbol = context.SemanticModel.GetDeclaredSymbol(oneMethodDeclaration) as IMethodSymbol; + + + var results = SearchForTargetExpression(context, methodSymbol, oneMethodDeclaration, searched, true); + + foreach (var oneResult in results) + { + if (!reported.Contains((oneResult))) + { + if (_directCallers.ContainsKey(oneResult)) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.InfiniteRecursiveCall, + oneResult.GetLocation(), methodSymbol.ContainingType.Name, oneMethodDeclaration.Identifier); + context.ReportDiagnostic(diagnostic); + reported.Add(oneResult); + + } + else if (_indirectCallers.ContainsKey(oneResult)) + { + var endPoint = _indirectCallers[oneResult]; + + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.InfiniteRecursiveCallRecursive, + oneResult.GetLocation(), methodSymbol.ContainingType.Name, oneMethodDeclaration.Identifier, oneResult); + context.ReportDiagnostic(diagnostic); + reported.Add(oneResult); + } + } + + } + } + + } + + + //TODO: Try to simplify this currMethodSyntax - it's very hard to follow + private IEnumerable SearchForTargetExpression(SyntaxNodeAnalysisContext context, + IMethodSymbol checkMethodSymbol, + MethodDeclarationSyntax currMethodSyntax, IDictionary searchedSymbol, bool isRoot) + { + foreach (var oneTargetExp in currMethodSyntax.DescendantNodes().OfType()) + { + /* + var oneTargetIdentifierExps = oneTargetExp.DescendantNodes().OfType(); + foreach(var oneTargetIdentifierExp in oneTargetIdentifierExps) + { + } + */ + + bool isContainedByBranch = false; + + SyntaxNode parent = oneTargetExp; + while (parent != currMethodSyntax) + { + if (parent is IfStatementSyntax || parent is SwitchStatementSyntax || parent is ForStatementSyntax || + parent is ForEachStatementSyntax || parent is WhileStatementSyntax) + { + isContainedByBranch = true; + break; + } + + parent = parent.Parent; + } + + if (isContainedByBranch) + { + continue; + } + + bool hasPrecedingReturn = false; + + foreach (var oneReturnSyntax in currMethodSyntax.DescendantNodes().OfType()) + { + if (oneReturnSyntax.SpanStart < oneTargetExp.SpanStart) + { + hasPrecedingReturn = true; + break; + } + } + if (hasPrecedingReturn) + { + continue; + } + + SymbolInfo oneSymbolInfo; + if (!context.TryGetSymbolInfo(oneTargetExp, out oneSymbolInfo)) + { + continue; + } + + var targetSymbol = oneSymbolInfo.Symbol as IMethodSymbol; + if (targetSymbol != null) + { + if (searchedSymbol.ContainsKey(targetSymbol)) + { + if (searchedSymbol[targetSymbol]) + { + yield return (ExpressionSyntax)oneTargetExp; + } + } + else + { + if (targetSymbol == checkMethodSymbol) + { + searchedSymbol.Add(targetSymbol, true); + if (isRoot) + { + _directCallers.Add(oneTargetExp, oneTargetExp); + } + yield return oneTargetExp; + } + } + } + } + + + var invocationExps = currMethodSyntax.DescendantNodes().OfType(); + foreach (var oneInvocationExp in invocationExps) + { + SymbolInfo oneSymbolInfo; + if (!context.TryGetSymbolInfo(oneInvocationExp, out oneSymbolInfo)) + { + continue; + } + + + bool isContainedByBranch = false; + SyntaxNode parent = oneInvocationExp; + while (parent != currMethodSyntax) + { + if (parent is IfStatementSyntax || parent is SwitchStatementSyntax) + { + isContainedByBranch = true; + break; + } + + parent = parent.Parent; + } + if (isContainedByBranch) + { + continue; + } + + var methodSymbol = oneSymbolInfo.Symbol as IMethodSymbol; + + if (methodSymbol != null) + { + if (searchedSymbol.ContainsKey(methodSymbol)) + { + if (searchedSymbol[methodSymbol]) + { + yield return oneInvocationExp; + } + } + else + { + var methodDeclarations = methodSymbol.DeclaringSyntaxReferences; + searchedSymbol.Add(methodSymbol, false); //let's assume there won't be any calls + + foreach (var methodDeclaration in methodDeclarations) + { + var theMethodSyntax = methodDeclaration.GetSyntax() as MethodDeclarationSyntax; + + if (theMethodSyntax != null) + { + var childResults = SearchForTargetExpression(context, checkMethodSymbol, theMethodSyntax, searchedSymbol, false); + + if (childResults != null && childResults.Any()) + { + searchedSymbol[methodSymbol] = true; //update the searched dictionary with new info + + if (isRoot) + { + _indirectCallers.Add(oneInvocationExp, childResults.First()); + } + + yield return oneInvocationExp; + break; + } + } + } + } + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.Designer.cs new file mode 100644 index 0000000..0da4656 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.Designer.cs @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.LogicError { + using System; + using System.Reflection; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class InfiniteRecursiveCallResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal InfiniteRecursiveCallResources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.LogicError.InfiniteRecursiveCallResources", typeof(InfiniteRecursiveCallResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Infinite recursive loop detected 的本地化字符串。 + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// 查找类似 The method {0}.{1}() calls {1}() again. This is an infinite recursive call. 的本地化字符串。 + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// 查找类似 The method {0}.{1}() calls {2} which eventually calls {1}() again. This is an infinite recursive call. 的本地化字符串。 + /// + internal static string MessageFormatRecursive { + get { + return ResourceManager.GetString("MessageFormatRecursive", resourceCulture); + } + } + + /// + /// 查找类似 Infinite recursive loop 的本地化字符串。 + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.resx new file mode 100644 index 0000000..299c102 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/LogicError/InfiniteRecursiveCallResources.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Infinite recursive loop detected + An optional longer localizable description of the diagnostic. + + + The method {0}.{1}() calls {1}() again. This is an infinite recursive call. + + + The method {0}.{1}() calls {2} which eventually calls {1}() again. This is an infinite recursive call. + + + Infinite recursive loop + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesAnalyzer.cs new file mode 100644 index 0000000..9e87a36 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesAnalyzer.cs @@ -0,0 +1,75 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace UnityEngineAnalyzer.Material +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class DoNotUseStringPropertyNamesAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.DoNotUseStringPropertyNames); + + private static readonly ImmutableHashSet materialStringPropertyMethods = ImmutableHashSet.Create( + "GetColor", + "GetColorArray", + "GetFloat", + "GetFloatArray", + "GetInt", + "GetMatrix", + "GetMatrixArray", + "GetTexture", + "GetTextureOffset", + "GetTextureScale", + "GetVector", + "GetVectorArray", + "SetBuffer", + "SetColor", + "SetColorArray", + "SetFloat", + "SetFloatArray", + "SetInt", + "SetMatrix", + "SetMatrixArray", + "SetTexture", + "SetTextureOffset", + "SetTextureScale", + "SetVector", + "SetVectorArray"); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var invocation = context.Node as InvocationExpressionSyntax; + if (invocation == null) + { + return; + } + + var name = invocation.MethodName(); + + // check if any of the methods are used + if (!materialStringPropertyMethods.Contains(name)) { return; } + + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + + var containingClass = methodSymbol.ContainingType; + + // check if the method is the one from UnityEngine.Material + if (containingClass.ContainingNamespace.Name.Equals("UnityEngine") && containingClass.Name.Equals("Material")) + { + if (methodSymbol.Parameters[0].Type.MetadataName == "String") + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotUseStringPropertyNames, invocation.GetLocation(), containingClass.Name, methodSymbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.Designer.cs new file mode 100644 index 0000000..7a95cd1 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Material { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotUseStringPropertyNamesResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotUseStringPropertyNamesResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Material.DoNotUseStringPropertyNamesResource", typeof(DoNotUseStringPropertyNamesResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Integer comparison is faster than string comparisons, Use Shader.PropertyToID("name");. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use integer proerty ID instead. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use integer proerty ID instead. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.resx new file mode 100644 index 0000000..c702a54 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Material/DoNotUseStringPropertyNamesResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Integer comparison is faster than string comparisons, Use Shader.PropertyToID("name"); + An optional longer localizable description of the diagnostic. + + + Use integer proerty ID instead + The format-able message the diagnostic displays. + + + Use integer proerty ID instead + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/MonoBehaviourInfo.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/MonoBehaviourInfo.cs index 7f5b981..9d7acce 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/MonoBehaviourInfo.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/MonoBehaviourInfo.cs @@ -19,6 +19,17 @@ class MonoBehaviourInfo "FixedUpdate", "LateUpdate"); + private static readonly ImmutableHashSet MoreUpdateMethodNames = ImmutableHashSet.Create( + + "MoreFixedUpdate", + "MoreSlowFixedUpdate", + "MoreUpdate", + "MoreUpdate2", + "MoreUpdate3", + "MoreLateUpdate", + "MoreLateUpdate2" + ); + public MonoBehaviourInfo(SyntaxNodeAnalysisContext analysisContext) { _classDeclaration = analysisContext.Node as ClassDeclarationSyntax; @@ -34,10 +45,10 @@ public MonoBehaviourInfo(SyntaxNodeAnalysisContext analysisContext) public void ForEachUpdateMethod(Action callback) { + var methods = _classDeclaration.Members.OfType(); + if (this.IsMonoBehaviour()) { - var methods = _classDeclaration.Members.OfType(); - foreach (var method in methods) { if (UpdateMethodNames.Contains(method.Identifier.ValueText)) @@ -46,6 +57,17 @@ public void ForEachUpdateMethod(Action callback) } } } + + if (this.IsMoreLoopBehaviour()) + { + foreach (var method in methods) + { + if (MoreUpdateMethodNames.Contains(method.Identifier.ValueText)) + { + callback(method); + } + } + } } public bool IsMonoBehaviour() @@ -70,5 +92,32 @@ private static bool IsMonoBehavior(INamedTypeSymbol classDeclaration) return IsMonoBehavior(baseClass); //determine if the BaseClass extends mono behavior } + + + + public bool IsMoreLoopBehaviour() + { + return IsMonoBehavior(_classSymbol); + } + + private static bool IsMoreLoopBehaviour(INamedTypeSymbol classDeclaration) + { + if (classDeclaration.BaseType == null) + { + return false; + } + + var allInterfaces = classDeclaration.AllInterfaces; + foreach (var oneInterface in allInterfaces) + { + if (oneInterface.Name.Equals("IMoreLoopBehaviour")) + { + return true; + } + } + + return false; + + } } } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsAnalyzer.cs new file mode 100644 index 0000000..0f7f473 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsAnalyzer.cs @@ -0,0 +1,83 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace UnityEngineAnalyzer.Physics +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UseNonAllocMethodsAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.UseNonAllocMethods); + + private static readonly ImmutableHashSet PhysicsAllocatingCasts = ImmutableHashSet.Create( + "BoxCastAll", + "CapsuleCastAll", + "OverlapBox", + "OverlapCapsule", + "OverlapSphere", + "RaycastAll", + "SphereCastAll"); + + private static readonly ImmutableHashSet Physics2DAllocatingCasts = ImmutableHashSet.Create( + "BoxCastAll", + "CapsuleCastAll", + "CircleCastAll", + "GetRayIntersectionAll", + "LinecastAll", + "OverlapAreaAll", + "OverlapBoxAll", + "OverlapCapsuleAll", + "OverlapCircleAll", + "OverlapPointAll", + "RaycastAll"); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var invocation = context.Node as InvocationExpressionSyntax; + if (invocation == null) + { + return; + } + + var name = invocation.MethodName(); + + List containingClassName = new List(); + + // check if any of the methods are used + if (PhysicsAllocatingCasts.Contains(name)) + { + containingClassName.Add("Physics"); + } + + if (Physics2DAllocatingCasts.Contains(name)) + { + containingClassName.Add("Physics2D"); + } + + if (containingClassName.Count == 0) + { + return; + } + + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + + var containingType = methodSymbol.ContainingType; + + // check if the method is the one from UnityEngine.Physics + if (containingType.ContainingNamespace.Name.Equals("UnityEngine") && containingClassName.Contains(containingType.Name)) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.UseNonAllocMethods, invocation.GetLocation(), containingType.Name, methodSymbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.Designer.cs new file mode 100644 index 0000000..0f504f6 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Physics { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class UseNonAllocMethodsResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UseNonAllocMethodsResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Physics.UseNonAllocMethodsResources", typeof(UseNonAllocMethodsResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to These calls have NonAlloc version since Unity 5.3. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' has non allocating version.. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This call has non allocating version.. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.resx new file mode 100644 index 0000000..9480c13 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Physics/UseNonAllocMethodsResources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + These calls have NonAlloc version since Unity 5.3 + An optional longer localizable description of the diagnostic. + + + '{0}' has non allocating version. + The format-able message the diagnostic displays. + + + This call has non allocating version. + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Properties/AssemblyInfo.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Properties/AssemblyInfo.cs index 6bfee81..1260997 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/Properties/AssemblyInfo.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("UnityEngineAnalyzer")] -[assembly: AssemblyCopyright("Copyright © Meng Hui Koh 2016")] +[assembly: AssemblyCopyright("Copyright © Meng Hui Koh, Vinny DaSilva 2016-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/RolsynExtensions.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/RolsynExtensions.cs index d2c84ee..03de4b8 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/RolsynExtensions.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/RolsynExtensions.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -65,5 +62,41 @@ public static bool IsOverriden(this MethodDeclarationSyntax methodDeclaration) { return methodDeclaration.Modifiers.Any(m => m.Kind() == SyntaxKind.OverrideKeyword); } + + public static string MethodName(this InvocationExpressionSyntax invocation) + { + + string name; + + if (invocation.Expression is MemberAccessExpressionSyntax) + { + name = ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier.ToString(); + } + else if (invocation.Expression is IdentifierNameSyntax) + { + name = ((IdentifierNameSyntax)invocation.Expression).ToString(); + } + else if (invocation.Expression is GenericNameSyntax) + { + name = ((GenericNameSyntax)invocation.Expression).Identifier.ToString(); + } + else + { + throw new ArgumentException("Unable to determine name of method. Invocation is of type: " + invocation.Expression.GetType()); + } + return name; + } + + public static T GetArgumentValue(this ArgumentSyntax argument) + { + //NOTE: Possibly add support for constant parameters in the future + + if (argument.Expression is LiteralExpressionSyntax) + { + var argumentValue = ((LiteralExpressionSyntax)argument.Expression).Token.Value; + return (T)argumentValue; + } + return default(T); + } } } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/DoNotUseStringMethodsAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/DoNotUseStringMethodsAnalyzer.cs index 3725ee2..d129df2 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/DoNotUseStringMethodsAnalyzer.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/DoNotUseStringMethodsAnalyzer.cs @@ -10,7 +10,7 @@ namespace UnityEngineAnalyzer.StringMethods [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseStringMethodsAnalyzer : DiagnosticAnalyzer { - private static readonly ImmutableHashSet StringMethods = ImmutableHashSet.Create("SendMessage", "SendMessageUpwards", "BroadcastMessage", "Invoke", "InvokeRepeating"); + private static readonly ImmutableHashSet StringMethods = ImmutableHashSet.Create("SendMessage", "SendMessageUpwards", "BroadcastMessage"); private static readonly ImmutableHashSet Namespaces = ImmutableHashSet.Create("UnityEngine.Component", "UnityEngine.GameObject", "UnityEngine.MonoBehaviour"); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.DoNotUseStringMethods); @@ -28,20 +28,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) return; } - - string name = null; - if (invocation.Expression is MemberAccessExpressionSyntax) - { - name = ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier.ToString(); - } - else if (invocation.Expression is IdentifierNameSyntax) - { - name = ((IdentifierNameSyntax)invocation.Expression).ToString(); - } - else if (invocation.Expression is GenericNameSyntax) - { - name = ((GenericNameSyntax)invocation.Expression).Identifier.ToString(); - } + var name = invocation.MethodName(); // check if any of the "string" methods are used diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingAnalyzer.cs new file mode 100644 index 0000000..a771b20 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingAnalyzer.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UnityEngineAnalyzer.StringMethods +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class InvokeFunctionMissingAnalyzer : DiagnosticAnalyzer + { + private static readonly ImmutableHashSet InvokeMethods = ImmutableHashSet.Create("Invoke", "InvokeRepeating"); + private static readonly string InvokeMethodTypeName = "UnityEngine.MonoBehaviour"; + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.InvokeFunctionMissing); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression); + } + + private void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + { + var invocation = context.Node as InvocationExpressionSyntax; + if (invocation == null) + { + return; + } + + var methodName = invocation.MethodName(); + + if (InvokeMethods.Contains(methodName)) + { + // check if the method is the one from UnityEngine + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + + var fullTypeName = methodSymbol?.ContainingType.ToString(); + + if (fullTypeName == InvokeMethodTypeName && invocation.ArgumentList.Arguments.Count > 0) + { + var firstArgumentExpression = invocation.ArgumentList.Arguments[0]; + + var invokedMethodName = firstArgumentExpression.GetArgumentValue(); + + var containingClassDeclaration = invocation.Ancestors().FirstOrDefault(a => a is ClassDeclarationSyntax) as ClassDeclarationSyntax; + + var allMethods = containingClassDeclaration?.Members.OfType(); + + var invokeEndPoint = allMethods.FirstOrDefault(m => m.Identifier.ValueText == invokedMethodName); + + if (invokeEndPoint == null) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.InvokeFunctionMissing, invocation.GetLocation()); + context.ReportDiagnostic(diagnostic); + } + } + } + + } + } +} \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.Designer.cs new file mode 100644 index 0000000..25dcb41 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.StringMethods { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class InvokeFunctionMissingResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal InvokeFunctionMissingResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.StringMethods.InvokeFunctionMissingResources", typeof(InvokeFunctionMissingResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The function being invoked does not exist. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The function "{0}" is invoking the method "{1}" that does not exist. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invoke Function is Missing. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.resx new file mode 100644 index 0000000..a33bf05 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/StringMethods/InvokeFunctionMissingResources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The function being invoked does not exist + + + The function "{0}" is invoking the method "{1}" that does not exist + + + Invoke Function is Missing + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj index fc59fdc..2c7a6bf 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj @@ -32,6 +32,12 @@ 4 + + True + True + DoNotUseStateNameResource.resx + + True @@ -50,6 +56,12 @@ True TypeGetTypeResources.resx + + + True + True + CameraMainResource.resx + True @@ -62,10 +74,22 @@ True DoNotUseCoroutinesResources.resx + + True + True + DuplicatedDelegateDetectionResource.resx + + + + True + True + ShouldCacheDelegateResource.resx + + True True @@ -83,6 +107,65 @@ DoNotUseForEachInUpdateResources.resx + + + True + True + DoNotGCAllocInUpdateResources.resx + + + + True + True + DoNotBoxWhenInvokeResource.resx + + + + + True + True + EnumShouldManualSetMemberValueResource.resx + + + True + True + DoNotUseEnumTypeParameterResource.resx + + + + + + True + True + UnsealedDerivedClassResources.resx + + + + + True + True + LambdaAnalyzerResources.resx + + + + + + True + True + StructAnalyzerResources.resx + + + True + True + InfiniteRecursiveCallResources.resx + + + + + True + True + DoNotUseStringPropertyNamesResource.resx + @@ -90,6 +173,12 @@ True DoNotUseOnGUIResources.resx + + + True + True + UseNonAllocMethodsResources.resx + @@ -98,8 +187,19 @@ True DoNotUseStringMethodsResources.resx + + + True + True + InvokeFunctionMissingResources.resx + + + + ResXFileCodeGenerator + DoNotUseStateNameResource.Designer.cs + ResXFileCodeGenerator DoNotUseReflectionEmitResources.Designer.cs @@ -112,10 +212,23 @@ ResXFileCodeGenerator TypeGetTypeResources.Designer.cs + + ResXFileCodeGenerator + CameraMainResource.Designer.cs + ResXFileCodeGenerator UseCompareTagResources.Designer.cs + + ResXFileCodeGenerator + DuplicatedDelegateDetectionResource.Designer.cs + Designer + + + ResXFileCodeGenerator + ShouldCacheDelegateResource.Designer.cs + ResXFileCodeGenerator EmptyMonoBehaviourMethodsResources.Designer.cs @@ -130,15 +243,60 @@ ResXFileCodeGenerator - DoNotUseForEachInUpdateResources.Designer.cs + DoNotUseCoroutinesResources.Designer.cs + + + ResXFileCodeGenerator + DoNotGCAllocInUpdateResources.Designer.cs + + + ResXFileCodeGenerator + DoNotBoxWhenInvokeResource.Designer.cs + + + ResXFileCodeGenerator + EnumShouldManualSetMemberValueResource.Designer.cs + + + ResXFileCodeGenerator + DoNotUseEnumTypeParameterResource.Designer.cs + + + ResXFileCodeGenerator + UnsealedDerivedClassResources.Designer.cs + + + ResXFileCodeGenerator + LambdaAnalyzerResources.Designer.cs + + + ResXFileCodeGenerator + StructAnalyzerResources.Designer.cs + Designer + + + ResXFileCodeGenerator + InfiniteRecursiveCallResources.Designer.cs + + + ResXFileCodeGenerator + DoNotUseStringPropertyNamesResource.Designer.cs ResXFileCodeGenerator - DoNotUseForEachInUpdateResources.Designer.cs + DoNotUseOnGUIResources.Designer.cs + + + ResXFileCodeGenerator + UseNonAllocMethodsResources.Designer.cs ResXFileCodeGenerator - DoNotUseForEachInUpdateResources.Designer.cs + DoNotUseStringMethodsResources.Designer.cs + + + ResXFileCodeGenerator + InvokeFunctionMissingResources.Designer.cs @@ -204,8 +362,12 @@ ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll False + + ..\..\..\..\..\..\..\..\Program Files\Unity2017.1.3p2\Editor\Data\Managed\UnityEngine.dll + + diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityVersions.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityVersions.cs new file mode 100644 index 0000000..b547fa4 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityVersions.cs @@ -0,0 +1,43 @@ +namespace UnityEngineAnalyzer +{ + public class UnityVersionSpan + { + public UnityVersion First { get; set; } + public UnityVersion Last { get; set; } + + public UnityVersionSpan (UnityVersion first, UnityVersion last) + { + First = first; + Last = last; + } + } + + public enum UnityVersion + { + NONE, + UNITY_1_0, + UNITY_2_0, + UNITY_3_0, + UNITY_3_5, + UNITY_4_0, + UNITY_4_1, + UNITY_4_2, + UNITY_4_3, + UNITY_4_4, + UNITY_4_5, + UNITY_4_6, + UNITY_4_7, + UNITY_5_0, + UNITY_5_1, + UNITY_5_2, + UNITY_5_3, + UNITY_5_4, + UNITY_5_5, + UNITY_5_6, + UNITY_2017_0, + UNITY_2017_1, + UNITY_2017_2, + UNITY_2017_3, + LATEST, + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index b655f2a..5fae71d 100644 --- a/readme.md +++ b/readme.md @@ -1,19 +1,57 @@ UnityEngineAnalyzer =================== + +![](https://raw.githubusercontent.com/donaldwuid/UnityEngineAnalyzer/master/Documents/usage.png) + UnityEngineAnalyzer is a set of Roslyn analyzers that aim to detect common problems in Unity3D C# code. Unity3D makes it easy for us to make cross platform games, but there are hidden rules about performance and AOT, which might only come with experience, testing or reading the forums. It is hoped that such problems can be caught before compilation. +Auto Add UnityEngineAnalyzer to your Unity project +--------------------- +Each time your refresh your unity project, the UnityEngineAnalyzer may be removed from the solution. +To auto add it to your solution, you can: +1. copy Editor/AutoAddUnityEngineAnalyzer.cs to your unity project Editor folder; +1. modify `public const string UnityEngineAnalyzerPath = "Tools\\VisualStudio\\UnityEngineAnalyzer\\UnityEngineAnalyzer.dll";` to your actual UnityEngineAnalyzer.dll path + +Implemented Analyzer +--------------------- +- Prefered to sealed the inherited class; 应对子类进行sealed +- struct should implement IEquatable, should override GetHashCode(); struct应实现IEquatable、应override GetHashCode() +- should cache delegate to avoid gc; 应cache delegate以防止产生gc +- avoid new object in Update() to avoid gc; 避免在Update()函数里new对象产生gc +- avoid boxing; 调用函数时应避免box产生gc +- should not use enum for generic; 泛型避免使用enum +- should use hash for Animator interfaces; Animator接口避免使用String,应使用Hash +- should use hash for Material interfaces; Material接口避免使用String,应使用Hash +- avoid calling Camera.main in Update-like message function; 避免Update()函数里调用Camera.main +- avoid calling GameObject.Find() in Update-like message function; 避免在Update()函数里GameObject.Find()之类的函数 +- should use CompareTag() but not string comparision; 应使用ComareTag(),而不是字符串比较 +- avoid using coroutine; 避免使用协程 +- avoid empty Update-like message function; 避免Mono空消息函数 +- avoid SendMessage(); 避免SendMessage +- detect some infinite recursive call; 错误无限递归检查 + + Comand Line Interface --------------------- -In order to use the Command Line Interface (CLI), download the latest release of UnityEngineAnalyzer then unzip the archive. +In order to use the Command Line Interface (CLI), download the latest release of UnityEngineAnalyzer then unzip the archive (https://github.com/vad710/UnityEngineAnalyzer/releases). 1. Open a Command Prompt or Powershell Window 1. Run `UnityEngineAnalyzer.CLI.exe ` 1. Observe the analysis results -1. (Optional) In the same location as the project file are `report.json` and `UnityReport.html` files containig the results of the analysis - +1. (Optional) In the same location as the project file are `report.json` and `UnityReport.html` files containig the results of the analysis + * Use command `-e customexporter exporter2 ...` to load custom exporters +1. (Optional) configuration file path. + * Use command `-c configureFilePath.json` to load custom configurations + * Configuration json, allows to enable / disable analyzers +1. (Optional) minimal severity for reports + * Use command `-s Info/Warning/Error` to defined used minimal severity for reporting + * Default is Warning +1. (Optional) Unity version for check + * Use command `-v UNITY_2017_1/UNITY_5_5/UNITY_4_0/...` to Unity version + * For default analyzer will try to find ProjectVersion.txt file and parse version automatically. Example: