1+ # Licensed under the MIT: https://mit-license.org/
2+ # For details: https://github.com/pylint-dev/pylint-ml/LICENSE
3+ # Copyright (c) https://github.com/pylint-dev/pylint-ml/CONTRIBUTORS.txt
4+
5+ """Check for proper usage of Matplotlib functions with required parameters."""
6+
7+ from astroid import nodes
8+ from pylint .checkers .utils import only_required_for_messages
9+ from pylint .interfaces import HIGH
10+
11+ from pylint_ml .util .library_handler import LibraryHandler
12+
13+
14+ class MatplotlibParameterChecker (LibraryHandler ):
15+ name = "matplotlib-parameter"
16+ msgs = {
17+ "W8111" : (
18+ "Ensure that required parameters %s are explicitly specified in matplotlib method %s." ,
19+ "matplotlib-parameter" ,
20+ "Explicitly specifying required parameters improves model performance and prevents unintended behavior." ,
21+ ),
22+ }
23+
24+ # Define required parameters for specific matplotlib classes and methods
25+ REQUIRED_PARAMS = {
26+ # Plotting Functions
27+ 'plot' : ['x' , 'y' ], # x and y data points are required for basic line plots
28+ 'scatter' : ['x' , 'y' ], # x and y data points are required for scatter plots
29+ 'bar' : ['x' , 'height' ], # x positions and heights are required for bar plots
30+ 'hist' : ['x' ], # Data points (x) are required for histogram plots
31+ 'pie' : ['x' ], # x data is required for pie chart slices
32+ 'imshow' : ['X' ], # Input array (X) is required for displaying images
33+ 'contour' : ['X' , 'Y' , 'Z' ], # X, Y, and Z data points are required for contour plots
34+ 'contourf' : ['X' , 'Y' , 'Z' ], # X, Y, and Z data points for filled contour plots
35+ 'pcolormesh' : ['X' , 'Y' , 'C' ], # X, Y grid and C color values are required for pseudo color plot
36+
37+ # Axes Functions
38+ 'set_xlabel' : ['xlabel' ], # xlabel is required for setting the x-axis label
39+ 'set_ylabel' : ['ylabel' ], # ylabel is required for setting the y-axis label
40+ 'set_xlim' : ['left' , 'right' ], # Left and right bounds for x-axis limit
41+ 'set_ylim' : ['bottom' , 'top' ], # Bottom and top bounds for y-axis limit
42+
43+ # Figures and Subplots
44+ 'subplots' : ['nrows' , 'ncols' ], # Number of rows and columns are required for creating a subplot grid
45+ 'subplot' : ['nrows' , 'ncols' , 'index' ], # Number of rows, columns, and index for specific subplot
46+
47+ # Miscellaneous Functions
48+ 'savefig' : ['fname' ], # Filename or file object is required to save a figure
49+ }
50+
51+ @only_required_for_messages ("matplotlib-parameter" )
52+ def visit_call (self , node : nodes .Call ) -> None :
53+ if not self .is_library_imported ('matplotlib' ) and self .is_library_version_valid (lib_version = ):
54+ return
55+
56+ method_name = self ._get_full_method_name (node )
57+ if method_name in self .REQUIRED_PARAMS :
58+ provided_keywords = {kw .arg for kw in node .keywords if kw .arg is not None }
59+ missing_params = [param for param in self .REQUIRED_PARAMS [method_name ] if param not in provided_keywords ]
60+ if missing_params :
61+ self .add_message (
62+ "matplotlib-parameter" ,
63+ node = node ,
64+ confidence = HIGH ,
65+ args = (", " .join (missing_params ), method_name ),
66+ )
67+
68+ def _get_full_method_name (self , node : nodes .Call ) -> str :
69+ func = node .func
70+ method_chain = []
71+
72+ while isinstance (func , nodes .Attribute ):
73+ method_chain .insert (0 , func .attrname )
74+ func = func .expr
75+ if isinstance (func , nodes .Name ):
76+ method_chain .insert (0 , func .name )
77+
78+ return "." .join (method_chain )
0 commit comments